Tag: 面向对象

为什么面向对象的编程是不好的编程范例。

好吧,让我们谈谈房间里的大象。 OOP在90年代初开始普及,那时候它是开发人员的圣杯。 但是现在,当项目变得越来越大时,它开始比解决常见问题更多地成为障碍。 我知道,没有什么是灵丹妙药,但是在函数式编程的出现(具有讽刺意味的是,它促进了许多OOP的良好实践),我们是否仍需要使用面向对象的编程作为选择的范式? 但是首先,让我们谈谈为什么经过这么多年OOP仍然如此受欢迎。 面向对象编程被Java所普及,Java是第一种广泛的面向对象语言。 为什么Java如此流行? 我认为这是几个方面的结合。 首先,它易于阅读,因为与其他语言相比,语法更简单,并且与英语相似(例如peter.love(angela) )。 其次,该程序在虚拟机上运行,​​因此开发人员无需考虑操作系统或CPU架构之类的问题。 第三,它不使用头文件,头文件的使用和维护使开发人员倍感头疼。 Java具有其特质,它通过降低入门级水平并使每个人的编码变得更加愉快,从而彻底改变了IT行业。 但是,那时的大多数项目都比当今的项目小得多且不那么复杂,并且随着时间的流逝,面向对象的方法变得越来越难以维护。 您可能会问:“但是这有什么问题呢?”。 我将答案分解为关键方面。 封装和依赖 OOP的规则之一是对象应像现实生活中的对象一样工作,例如小吃机(项目)包含许多子元素(对象),例如硬币槽,允许用户抛硬币(输入功能)并通过有关它的信息到键盘(异步输出信号)。 但是,大多数对象与现实生活中的对象并不相似,它们更加复杂并且具有许多依赖关系,从而使它们的封装方面无效。 在过去的很多次中,当我从事新功能的开发时,我有一个想法可以重用几年前工作过的另一个项目中的一些代码。 从OOP角度来看,一个好主意是仅从另一个项目中获取一个对象并在当前项目中使用它,因为根据面向对象的编程,对象应该是独立的封装子部分,我可以在任何地方重用我想要。 因此,我打开了一个先前的项目并进行了复制,但是,哦,不……该对象与其他两个对象有关系,因此我也复制了这两个对象。 但是,这些对象之一使用的自定义类型是在该旧项目的其他位置创建的。 这种情况可能会持续很长时间,并且由于存在依赖性问题,您可能被迫复制大量不需要的对象或大量代码。 在这种情况下,我只是说“操”,并编写了一个新的类似功能,该功能更适合新项目。 但是,我花在尝试重用理论上“可重用”的代码上的时间被永远浪费了,并且对当前项目没有任何价值。 当项目增长时,即使潜在的简单对象在依赖关系网中纠结在一起,也越来越有可能使它们变成一团糟,这使它们在项目外部完全无法使用。 乔·阿姆斯特朗(Joe Armstrong)将这个问题描述如下: 面向对象语言的问题在于,它们具有随身携带的所有隐式环境。 您想要香蕉,但是得到的是一只大猩猩,拿着香蕉和整个丛林。 乔·阿姆斯特朗 关系层次结构 这是与上一个类似的问题。 随着项目的发展,关系层次结构变得越来越大,越来越复杂。 这导致如下情况: 考虑这种情况。 对象J需要一些由对象G生成的数据,但它们都位于单独的“分支”上。 在这种情况下,开发人员应该怎么做? 有几种不同的解决方案来解决此问题。 从OOP角度来看,最好的解决方案是将请求通过对象I和C传递到最近的公共关节(在这种情况下为对象A),然后该“主”对象将通过对象B将请求发送给对象G。这个请求的请求也必须从对象G整个传递到J。如您所见,这是一场噩梦。 为了简化这种混乱,您可以做两件事。 您可以100%忠于OOP范例,并要求您的经理额外的时间来重构此功能,并查看他的脸会变得多红。或者,您可以进行简单的横切并直接连接这两个对象。 但是,从长远来看,这可能会导致许多问题。 最明显的现象之一是,创建跨领域连接会使代码更复杂,更难以阅读和维护。 但是,更危险的问题与核心对象特征之一有关。 对象包含两个元素组,内部状态(变量)和函数。 当两个对象要使用从属对象中的数据时(在这种情况下,这是直接相关的对象B和对象J通过交叉切割而相关联),则该对象可能开始产生意外结果。 为防止这种情况,开发人员必须花费更多时间在该对象内部创建某些功能,以防止其生成无效/意外/错误的结果。 经理,服务,工厂,助手等 大型项目的另一个问题是称为管理器,服务,助手等对象的数量在不断增长。它们的创建是为了封装一些功能,这些功能将用于多个不同的对象中。 如果将它们作为私有实体保留在这些对象中,则是一个好主意,因为它可以防止出现共享状态问题(在上一段中进行了介绍)。 但是,在大多数情况下,它们是在“主”对象的整个生命周期内存储的,即使“主”对象不使用它们也会增加内存使用。 更大的问题是管理者拥有一个共享实例,而这仅仅是一个单例。 这种方法是一个很大的错误,不应在任何地方使用。 […]

Swift Universe中的迭代器设计模式

(本文最初是用俄语撰写的,并在此处发布。) 迭代器是设计模式之一,通常不为程序员所注意,因为其实现细节通常嵌入在编程语言的标准库中。 但是,它也是四人帮经典著作“设计模式:可重用的面向对象软件的元素”中描述的行为模式之一。 它的理解永远不会被淘汰,甚至可能会有所帮助。 迭代器是一种提供对复合对象的所有元素(通常是容器类型,例如数组和集合)的串行访问的方法。 语言内置资源 创建一个数组: 让numbersArray = [0,1,2] …并循环遍历: 代表numbersArray { 打印(数量) } ……这是很常见的事情,尤其是对于像Swift这样的现代编程语言而言。 但是,此功能由实现Iterator设计模式基础的代码支持。 在Swift中,一种类型必须符合Sequence协议才能被for循环迭代。 除其他事项外,此协议要求具有关联的Iterator类型(必须符合IteratorProtocol )并实现结构方法makeIterator() (此类型返回此类型的特定迭代器): 协议序列{ relatedtype Iterator:IteratorProtocol func makeIterator()-> Self.Iterator //另一个要求在这里… } IteratorProtocol包含唯一的方法— next() ,该方法按顺序返回以下对象: 协议IteratorProtocol { 关联类型元素 变异func next()-> Self.Element吗? } 感觉好像很多复杂的代码,但实际上并非如此。 我们将在一段时间后确定。 例如, Array转换为Sequence (虽然不是直接,但通过协议继承链: MutableCollection继承自Collection ,而Collection继承自Sequence ),这就是为什么可以通过for循环来标识其实例的原因。 用户类型 如果我们的类型必须是可迭代的,我们该怎么办? 像往常一样,显示示例更容易。 让我们定义用于存储书本的书架的类型: 结构书{ 让作者:字符串 让标题:字符串 } […]

当存在具有相同名称的全局函数时,请小心Swift Extension

在Swift中实现Object Seam时的问题 要认识的基本问题是,当我们在面向对象的程序中查看调用时,它没有定义实际要执行的方法。 现在,您具有以下代码。 一个类调用全局函数。 现在,您要实现对象接缝。 以下实现将产生完全不同的结果。 B中的扩展是单元测试模块,而A中的扩展是其原始模块。 当您运行它时,在其他模块中实现的Swift扩展在动态调度中将具有较低的优先级。

了解Swift的静态属性

静态属性是类级别的属性。 一个类的所有实例共享该类的相同静态属性。 最近,我的团队在UIAlertController上实现了扩展,以将重复的警报控制器代码块变成一个衬里。 他们遇到了一个问题,即用于打开“设置”的自定义UIAlertAction只能工作一次,但此后再也不能工作。 我对其进行了进一步研究,并意识到问题在于UIAlertAction的定义。 它定义为静态常数。 最初的猜测是问题出在处理程序之内。 因为处理程序正在正确执行(每次都不会执行),所以我认为这是错误的。 我记得斯坦福大学CS193p的一个项目“集中”。 在Concentration中,“ Game”类具有静态变量,所有实例均引用该属性。 在扩展中,每个单独的自定义UIAlertAction被定义为静态的。 我意识到这一定是个问题。 创建一个类级别的UIAlertAction并在其第一个警报调用时执行其处理程序,但随后的所有警报均引用该已执行的处理程序。 我的解决方案是将UIAlertAction定义为计算的非静态变量。 (扩展位于UIAlertController上,扩展内是自定义的静态UIAlertActions)。 这意味着,每次使用“ openSettings”动作对“ presentAlert”(扩展中的主要功能)的调用都将返回该动作的唯一实例。 为了适应团队的偏爱,使用了每次返回一个新实例变量的静态变量。 他们指出,返回实例变量(多个内存分配)的静态变量(单个内存分配)可能会引起误解,因此对此的更好替代方法是返回实例变量的静态函数。 计算机科学拥有许多健康的概念,在研究它们时似乎只不过是抽象,因此在实际的,可观的应用程序中发现一个价值始终是一件好事。 尽管作为开发人员,我们有时有时可以使XYZ正常运行,但花一些时间通过诸如Concentration之类的项目来尝试基本概念仍然可以证明是非常有用和省时的。

4行代码的iOS录音机

从Creo 2.0.4开始,新的AudioRecorder类已可用。 通过此类,开发录音机非常简单。 我们将通过在主设计板上添加AudioRecorder类(将自动创建一个AudioRecorder1实例),然后添加三个按钮来开始/停止录制以及播放录制的音频,从而开始本教程: Timer1 Action事件如下所示: 现在,我们要在Chart控件中可视化归一化的值流(从0到100)。 因此,让我们开始创建一个数据集,该数据集以后可以由我们的图表使用。 从控件列表中将DataSet对象拖放到Window1中,并将其重命名为更具描述性的AudioDataSet。 设置自定义数据集非常简单,需要填写三个事件: 行 (此事件应返回数据集中可用行的数量) 列 (此事件应返回数据集中可用列的数量) 值(行,列) (此事件应基于行/列参数返回实际值) 为了使我们的代码尽可能简单,我们的数据集将基于数组,并且我们将定义数组的最大大小,一旦超过该大小,便会从头开始重新使用数组(我们不需要历史记录图表的数据,我们只需要显示当前值)。 满足所有这些要求,我们可以从向AudioDataSet对象添加三个属性开始: r = [] (这是我们的数组) maxSize = 10 (这是r的最大大小,该值应在运行时计算(设备框架宽度/条项目宽度) i = 0 (要写入r数组的下一个值的索引) 我们还需要添加一个辅助函数以将值添加到我们的r数组中。 我们的addValue(v)方法如下所示: 需要正确配置Chart1,我将所有详细信息的描述保留在项目文件中。 这里只是一些注意事项: 数据集必须配置为AudioDataSet 必须将“图表类型”设置为“条形图”,并且必须取消选中“使用动画”复选框(我们将每秒更新此图表10次,在这种情况下动画将成为瓶颈) 自动范围必须禁用 现在,我们有了应用程序中所需的所有对象。 仅错过了最后一步,在Timer1 Action事件中,我们需要将计算值添加到AudioDataSet,然后重新加载Chart1。 Timer1 Action事件如下所示: 最终应用看起来像: 可以从我们的GitHub存储库下载Creo项目:https://github.com/creoapp/examples/raw/master/AudioRecorder.zip

iOS和Swift Universe中的访客设计模式

(本文最初是用俄语撰写的,并在此处发布。) 访客是由四人帮(GoF)在其经典著作“设计模式:可重用的面向对象软件的元素”中描述的行为设计模式之一。 简而言之,当需要对一组不同类型的对象执行一些类似的操作时,此模式可能很有用。 或根据对象的特定类型执行操作,而不知道该类型。 换句话说,模式可以通过添加相似或具有相同来源的操作来扩展一组类型的功能。 而且,类型结构和实现不会改变。 当然,最简单的解释方法是通过示例说明其用法。 无论如何,我想修正一个事实,即所提供的所有示例和代码段并非取自我的工作经验,而是出于学术目的。 即,本文试图使您熟悉一种 面向对象的技术, 而不是讨论要解决的特殊问题。 我想说的第二件事是,这里的所有代码都是为了更好地理解而结构化的,并且可能存在各种甚至明显的缺陷。 例 假设我们有一个UITableViewController ,它使用了几个UITableViewCell子类: ClassCell类:UITableViewCell {/ ** /} SecondCell类:UITableViewCell {/ ** /} class ThirdCell:UITableViewCell {/ ** /} TableVC类:UITableViewController { 覆盖func viewDidLoad(){ super.viewDidLoad() tableView.register(FirstCell.self, forCellReuseIdentifier:“ FirstCell”) tableView.register(SecondCell.self, forCellReuseIdentifier:“ SecondCell”) tableView.register(ThirdCell.self, forCellReuseIdentifier:“ ThirdCell”) } 覆盖func tableView(_ tableView:UITableView,cellForRowAt indexPath:IndexPath)-> UITableViewCell { / ** /返回FirstCell() / ** /返回SecondCell() […]

依赖注入iOS让我们变得简单

依赖注入:拆分此英语术语以获取更简便的方法 依赖-您所依赖的东西 注入-用于注入东西 所以依赖注入意味着注入需要的依赖而不是创建它 或者我们也可以说将依赖注入到对象中,而不是负责创建对象依赖的任务 。 或某些怪胎将其定义为为对象提供实例变量,而不是在对象中创建实例变量。 😛 让我们以一个例子来获得更清晰的图像: 在这里的例子 关键是MyViewController负责创建Service实例。这意味着MyViewController类不仅了解Service类的行为。 它还知道其实例化。 现在在下一个DI示例中,我们在创建对象时将Service实例注入MyViewController实例。 尽管最终结果可能看起来与上面的示例相同,但事实并非如此。 通过从外部注入Service , MyViewController不知道如何实例化Service 。 简单来说,这就是依赖注入。 通常,依赖注入被认为是3种类型: 通过初始化程序进行依赖注入: 也称为基于构造函数,最优选的 是在初始化期间在构造函数 (构造函数) 中传递依赖项。在初始化程序中,通过将依赖项的属性声明为常量(使用let)来使基于DI的依赖项不可变。让它在整个客户的一生中都发生变化。 因为要求我们在初始化期间将服务作为参数传递,所以指定的初始化程序清楚地显示了DataManager类的依赖项。 通过属性进行依赖注入: 在类中声明的Internal或Public属性也可以用于将依赖项注入到类对象中。 这似乎很方便,但是它增加了一个漏洞,因为可以修改或替换依赖关系。 换句话说,依赖关系不是一成不变的。 使用serviceManager属性将服务依赖项传递给类MyViewController的viewController实例。 通过方法进行依赖注入: 依赖项也可以在需要时注入。 通过声明一个将依赖项作为参数接受的方法,这很容易做到。 在下面的示例中,该服务不是DataManager类的属性。 而是将服务作为方法的参数注入。 即使DataManager类失去了对依赖项,服务的控制,这种类型的依赖项注入也带来了灵活性。 为什么要使用依赖注射:- 测试:依赖注入使开发人员可以用模拟对象替换对象的依赖关系,从而使单元测试更易于设置和隔离行为。 透明度:通过注入对象的依赖关系,类或结构的职责和要求变得更加清晰和透明。 松耦合:协议和依赖注入的使用可以减少项目中的耦合。 协议在Swift中非常有用且用途广泛。 这是协议真正发挥作用的一种情况。 关注分离 :依赖注入严格地分离了对类的关注。如果类不负责实例化依赖实例,则它不需要知道如何做到这一点。 即使它与依赖关系的行为有关,但也不应与其实例化有关。 我们得出结论: 依赖注入是您在项目中可能已经在使用的东西,它的好处确实使结构易于理解,可重用,可测试和可维护,因此不要再键入几行代码,而是开始使用聪明的想法。 快乐编码 保持冷静和智能代码

从Objective-C到Swift的过渡

我已经为Objective-C编码了很多年了,对此感到非常满意。 我很高兴将代码放在方括号中。 在Apple在iOS 5.0上引入ARC之前,我什至设法处理了所有我们不得不使用的保留和发布内容。 也许这就是为什么我推迟了我作为Swift开发人员的出场时间。 现在,我们已经将项目迁移到Swift,我看到了这么长时间以来我所缺少的所有收获。 但是,在过渡过程中必须将两种语言结合在一起时,并不是所有事情都变得容易。 这篇文章将涵盖在此过程中做出的一些决定和遇到的问题。 迭代和增量方法 我们的PromoFarma iOS项目是在VIPER架构下使用Objective-C构建的,该项目将所有实现隐藏在协议中,以实现层边界和交互。 有很多资源讨论了干净架构的许多好处,但是我想强调一个: 层隔离 。 这种层隔离使我们能够逐层计划过渡。 从功能角度来看,一次转换每个图层实际上是一种迭代和增量的方法。 我们能够一次面对一个问题,并且在测试过程中只需进行较少的更改即可测试每个功能。 还应该指出,由于这种方法,我们不断地定期发布版本。 快速发挥潜力:Objective-C合规 为了遵循增量过程,我们将所有层边界协议都保留在Objective-C中 。 这样,所有的Objective-C代码都可以基本保持完整。 当然,这意味着所有新的Swift代码都必须符合Objective-C并必须符合其协议。 因此,第一个Swift实现无法利用其某些功能,例如结构,元组,泛型或高级枚举。 这样做的好处是,它帮助我们遵循了最初的迁移计划。 零或不零 在Objective-C中进行编码时,会冒着习惯将nil视为可使用的有效值的风险。 是的,当不应该为nil 值为 nil ,应用程序不会崩溃。 但是,除非您进行处理,否则它也不会按预期工作。 关于Swift的一件好事是,它迫使您在编码时决定一个值是否可以为nil 。 因此,确定所有内容的可空性会使整个迁移比仅将一种语言翻译成另一种语言慢一些。 但是最终,它变成了一个更可靠的应用程序。 再见Mantle和AFNetworking 迁移从实体层开始,因为它是最简单且依赖程度较低的实体 ,这是第一个大问题。 我们使用的是Mantle ,感觉就像将它保留在Swift中就像是逆流而上。 因此,我们决定实施Swift的内置Codable协议,这是第二个大问题。 当我们在数据层中使用AFNetworking时,对可Codable对象进行反序列Codable的过程并不简单。 然后我们尝试了一下,开始使用Alamofire迁移数据层 。 在中期,这是一个不错的决定,但是迫使我们稍微改变增量方法,并同时迁移实体和数据层 。 它不仅是语言翻译,还必须对每个文件进行一些更改。 扩展…无处不在的扩展 在这两层迁移之后,下一层就很简单了。 不仅因为Presenter,Interactor和Routing层非常简单,而且还因为团队对语言本身有了信心。 值得注意的是,尤其是在Presenter层中 ,我们已经能够按照其所遵循的每个协议在扩展中组织代码,从而提供了很多代码可读性和更好的组织性。 你好泛型 从我作为Android开发人员的经验中,我在Objective-C中一直想念的一件事就是通用类型。 […]