Swift develop

闲聊常见的设计模式

2020-01-13  本文已影响0人  张小西的BUG

纠结了下,不太想班门弄斧,但是呢,每次也不想抱着这么厚的一本书啃,所以就当自己总结下吧。《Head First》设计模式,主要讲述的是OOP下常见的设计模式和一些OOP设计规范,全书用java写的示例代码,但基本也能看懂。关于设计模式,刚入这行的时候大概听的比较多后来写的代码多了也就没啥想法了。光靠看书是学不来的,要用宏观的整体架构,需要结合实际的具体应用场景,每个人的理解也各有千秋没有对错,但是一味的去套用设计模式是大忌。


书中讲到第一个模式是策略模式:定义了算法族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。这段是书中的原文解释。策略模式举的例子是扩展一个‘鸭子’类采用的是组合,独立出鸭子的特征类,将算法封装到特征类当中,由‘鸭子’去组合成不同特称的类。没用使用继承,全书都没怎么去介绍继承,貌似大多数设计模式都是为了解决继承所带来的缺点,在OOP中继承被滥用了,导致很多开发在调试遇到超级父类,是很头痛的,很多语言不支持多继承,这也缚束了对类的扩展功能,继承的层次太深,自己都可能整不明白子类的功能,当然也有多继承的语言比如C++,但是也有其他的问题。
提到的OOP设计原则:


提到第二个设计模式是观察者模式:在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新。主要作用是用来解耦(原文用的是松耦合的威力来形容的),被观察者称为主题,当二个对象(主题和观察者)之间松耦合,它们依然可以交互,但是不太清楚彼此的细节。
举的例子是观察更新天气显示。当然也提到了一些其他的问题针对java因为在java中使用内置的观察者是一个类而不是接口,使用时需要去继承,因为不支持多继承,所以限制了观察者的扩展。所以更多的是自己去定义接口实现观察者模式。
观察者模式在其他框架中也是被大量的使用要详细去介绍又要浪费一波口水。比如在OC中,要深入去了解实现观察者模式,是一个可以讨论很久的话题了。但是必要时可以自己去实现观察模式,不用使用默认提供的实现方式。


提到第三个设计模式是装饰者模式:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。这章开头就提到“将再度探讨典型的继承滥用问题”,我就郁闷了继承就得罪人了么,OOP中四大基础,抽象,封装,多态,继承,只要继承被拿出来“鞭尸”了。举的列子是,计算星巴克咖啡的价格。
但是有点我有点蒙,提到装饰者和被包装者必须是一样的类型,也就是有共同的超类。但是是利用继承来达到“类型匹配”,不是利用继承来获得“行为”。为啥非要有共同的超类呢?如果不是的话,难道就是另外一种设计模式了吗?在实际的开发中我们确实会组合对象来获得新的功能/行为,有时很难鉴定使用的是装饰模式还是组合模式,但我觉得这点不重要了,结构清晰容易扩展就行。
当然严格按装装饰模式来些代码的话,很难受(个人感觉),更多的是喜欢用组合,文中也提到了组合和继承(装饰)的概念,提到这,主要是引出了一个高大尚的说法:“只有在针对抽象组件类型编程时,才不会因为装饰者而收到影响,如果的针对特定的具体组件编程,就应该重新思考你的应用架构,以及装饰者是否合适”。在后面的模式中也将到了组件依赖关系。文中提到java I/O大量的使用装饰者,引出了装饰者的一个缺点:利用装饰者模式,常常造成设计中有大量的小类,数量实在太多,可能会造成使用次API程序员的困扰。当然解决办法就是:装饰者+Factory+Builder结合使用来简化代码复杂度。


提到第四个设计模式是工厂模式:定义了一个创建对象的接口,但是由子类决定要去实例化的类是哪一个。工厂方法让他们把实例化推到子类。文中介绍的篇幅很长,主要分为三大部分:简单工厂,工厂方法,抽象工厂。所有的工厂模式都是为了减少应用程序和具体类之间的依赖,帮助我们针对抽象编程,而不要针对具体类编程。
简单工厂:虽然不是真正的设计模式,但仍不失为一个简单的方法,可以将客户程序从具体类解耦。分为二种,一是直接使用静态方法创建指定的对象,这个就不能通过继承来改变创建方法的行为,事实上在创建基础控件时大多数我们都会这么干,并没有什么大的问题,还很方便,OC中的UIButton类的创建。二是单独封装一个类通过传入的类型(字符串/枚举)来创建对应的对象(所创建的对象都有共同的超类)
工厂方法: 使用继承,把对象的创建委托给子类,子类实现工厂方法来创建对象。这个有点难理解,就文中提到的例子来解释吧。文中是创建‘披萨’,定义一个创建‘披萨’的工厂抽象类,申明创建‘披萨’的API和创建子类工厂的API。然后再定义创建不同地区‘披萨’的子类工厂,然后由子类的工厂去创建真实的‘披萨’,创建不同地区的‘披萨’就是用对应地区的工厂。
抽象工厂:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。从定义上来看是基于工厂方法引入新的工厂方法,相关或依赖封装到抽象类中,达到解耦的目的,听起来很有意思,这个是在解决比较复杂的问题才会用到的。
文中提到简单工厂和工厂方法的差异, 前者返回的是超类,后者返回的是子类,但是工厂方法创建的是一个框架让子类决定如何实现,更有弹性的去扩展,因为简单工厂不能变更正在创建的产品。
在抽象工厂模式引入了一个比较重要的设计原则:依赖倒置原则,指导我们避免依赖具体类型,而要尽量依赖抽象。再具体点就是指的:不能让高层组件依赖低层组件,不管高层或者底层组件,二者都应该依赖抽象。
关于工厂模式,看完表示有点蒙,在最开始以为是很简单,到后面的介绍,还是很麻烦,对于抽象,组合抽象(抽象工厂),不谈熟练的使用,最基本的能在实际的应用中能想到要抽象出来,分离程序和具体类之间的耦合,已经很不错了,好吧,任重而道远。


提到的第四个设计模式单件模式(Singleton Pattern):确保一个类只有一个实例,并提供全局访问点。就是我们常说的单例,在实际的开发中有时候也被滥用。�
实现单例不同的语言实现方式也是大同小异,都是用static修饰,需要注意下多线程问题。在这里提下swift创建单例,官网给的例子用static修饰后创建的对象没有多线程问题,但是OC中是需要使用到线程锁。
至于单例模式的使用场景,那就非常多了。不管是系统使用,还是我们实际的应用场景中,在此就不具体展开说,主要的是刚才提到的不要滥用,仅在需要被用来管理共享资源时可以考虑使用,因为单例不会被释放,比较占资源。


提到的第五个设计模式命令模式:将 请求封装成对象,以便使用不同的请求,队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。 从定义上来理解就是将请求对象和执行请求的对象解耦,二者是通过命令对象来沟通。
既然是通过命令对象来沟通,必然我们需要中间对象(command)来调用执行方法和撤销方法。书中介绍的是实现遥控器开控制灯的开关。三个大对象,第一个遥控器(记录各种命令),第二个封装的命令对象(需要依赖注入),第三个各种灯(用来初始化命令对象)。设计的流程:1.先初始化灯;2.创建电灯命令对象(对应的灯,和执行方法execute);3.将命令加入到遥控器中,三个参数(卡槽的位置,打开的命令,关闭的命令)。最后就是测试了,点击卡槽时,判断位置是否有对应的命令,有的话,取出来执行exexute方法。流程解释的很简单,只是抛砖引玉,自己也不太会用这个设计模式,感觉在复杂的流程中会使用到,文中提到的比如:日志安排,线程池,工作队列等。
再来加入撤销的功能,首先在遥控器对象中记录最后一次执行的命令commond,然后每一个命令对象需要提供一个undo的方法(也就是撤销的方法),然后撤销时执行commond.undo()。
理解的比较肤浅,也暂时这样了,毕竟这块没有太多的实战经验。。。。


提到的第六个设计模式适配器模式与外观模式:适配器模式是将一个类的接口转换成顾客期望的另一个接口。适配器让原本不兼容的类可以合作无间。外观模式是提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。
这个设计模式应该用的比较多,但你可能没有发觉。比如:我们经常会引用第三方的SDK,这时候我们就需要写一个适配器了,将第三方的SDK的API,封装到一个可以直接方便调用的类,第三方的SDK不会直接和其他模块有交互。还有比如我们混编的时候,以及做版本适配的时候都可以考虑使用适配器。
文中提到了适配器,有二种,一种是针对对象的,我们大多数用到的都是这种,还有就是针对类的这个是比较麻烦,提到了需要使用到多继承,所以我也就没仔细去看了。再提到了适配器和装饰者的差异(个人还喜欢把组合拿出来一起说),这三种设计模式都是相同点,扩展类的行为方式,但是方式有所不同,适配器是通过转变接口,装饰者在包装者中加入新的责任,组合模式通过对象组合完成实现更强大的功能。(反正都是在推翻继承)
外观模式,从文中举的例子比较好理解,当要去看电影需要做的一系列流程,通过调用一个API去全部完成,而不依赖过程中的每一个对象。当我看到这块的时候,我觉得我们写组件的时候不就是像搭积木一样么,分成每一块,每一块都可以单独使用,然后通过外观模式,一次性调用这些‘积木’的API使整个组件更为方便的使用,功能更强大,而不用关心底层对象怎么去交互的。文中也提到了一点,也是我后来在组件时尽可能去遵循的原则:减少依赖对象。


提到的第七个设计模式模版方法模式(封装算法):在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模版方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
和策略模式有点类似,对的,文中也反复提到了和策略模式的差异,从字面意思和定义来理解,以及文中提到的例子,不同类型的咖啡的冲泡方法法,需要完成的步骤是一样的,但是每一步具体操作是不一样的,然后我们可以把这些步骤定义(抽象)为一套模版也称为流程,让子类自己去具体实现每一步怎么做,所以模版模式强调的是抽象(感觉有点类似协议或者说抽象接口)。而策略模式只是将算法封装起来,让客户可以轻易的使用不同的算法,经常使用对象组合,所以策略模式强调的是封装。
文中还提一个概念钩子:是一种被申明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。有点AOP(面向切面编程)的感觉(iOS中hook是基于AOP编程思想实现的),好吧,这块是一个大的知识块,不在此介绍,因为我还没有深入去了解😢。
再看看文中提到钩子的作用(自己平时没怎么用过啊,也就只能照搬了)。子类的算法中有可选的部分可以使用钩子来选择性的执行这个算法,文中的钩子方法返回的时bool值,默认返回true,子类覆盖钩子方法用来决定某一步算法是否需要执行。有个高大尚的说法,钩子让子类能够有机会对模版方法中即将发生的(或刚刚发生的)步骤作出反应。表示有点蒙,这个就过了,后面学习AOP时,可能会更有体会。


提到的第八个设计模式迭代器与组合模式:迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而不暴露其内部的表示。组合模式允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
最初看的时候有点奇怪(看完了还感觉奇怪,可能我对java不太了解,没有太好的实际应用场景),迭代器为啥和组合模式放到一起?前面不是有很多地方有介绍组合模式的么?迭代器模式在实际中有啥作用,仅仅只是遍历循环么?文中提到定义一组接口用来表示迭代器:next(),hasNext().,remove(),其中remove()是非必须实现。
先看第一个问题,为啥放到一起了,迭代器本身就是访问聚合对象中的每一个元素,聚合对象中可能就是用的组合,在后面提到了组合迭代器,引入了递归的概念,通过递归去遍历。(个人理解得比较肤浅)
第二个问题,此组合非彼组合,先暂时这么理解,肯定是有差异的。之前提到的组合模式,更偏向于功能扩展,是为了更有弹性的扩展一个类的功能和责任,用来代替继承。这个章节提到的组合模式,更偏向于一种数据结构(树形结构),组合对象需要必须要实现相同的接口,为了更好的使用迭代器来遍历,文中提到的‘迭代器组合凑在一起的魔力’,实际上使用的是递归。
第三个问题,想先肯定回答是仅仅遍历循环的作用(个人理解)。但是这种解决问题的方式值得称得上为模式,首先这种遍历不是一个单单的for/while循环就能轻松解决,它访问的是一个聚合类,文中提到了单一责任这个设计原则:一个类应该只有一个器变化的原因。但是区分设计中的责任是一件很困难的事。其次就是定义的接口是一种在解决实际问题中经常会用到的:first(),next(),isDone(),currentItem()不同的语言大概定义的有所差别,但是思路都是相通的。
文中提到的迭代器和组合是解决各种菜单组合在一起的问题,实际中我们可以结合具体的应用场景来灵活的使用各种设计模式,不要定格使用某种设计模式。


提到第九个设计模式状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。文中开始提到了策略模式,整章也是基本围绕着这二种设计模式来讲解的。
不打算过多的总结这章类容了,字面很好理解,再其次,有时候设计模式并不一定是我们的首选,设计模式也有自身的缺陷,有时候会增加代码的复杂度,比如这章节的状态模式,如果使用函数式编程思想我觉得代码复杂程度会较少很多。事实上结合具体的应用场景,甚至不一定要首选面向对象,灵活的使用编程的思想有时候会使代码更加整洁,因为设计模式写出来的代码很有层次结构,从抽象/接口到子类再到多态,实际上很多问题不需要很多的层次结构强行使用设计模式反而会使问题复杂化。


提到的第十个设计模式代理模式:为另一个对象提供一个替身或占位符以访问这个对象。也就是控制对象访问(可以用来解耦合),解释比较简单,或者说比较官方,不管你做啥开发,如果你对代理模式不熟悉,真的建议你不要写代码了😢,不管是系统的框架或者我们自己写的组件,基本都会大量用到代理模式。
不过多解释这章了,区分下代理和协议就行了,代理是控制对象访问权限,协议(策略模式中用到)为对象加上行为。


后面还有一章讲述的是符合模式,不总结了,感觉东西有点多,需要具体结合实际的问题来多想想了。在这个各种编程思想和设计模式横空出世的时代,没有对错,存在即合理,只有是否合适,知识是死的,只有当用来解决问题时,学到的知识才是真的东西。问题是活的,各种复杂的问题,都需要去面对,问题不大。

上一篇下一篇

猜你喜欢

热点阅读