signed

QiShunwang

“诚信为本、客户至上”

读大话设计模式有感

2021/6/3 13:08:55   来源:

体会

在编程的过程中,要时刻考虑程序的功能复用性,并将其封装起来
开发人员应该仅对程序中呈现出频繁变化的那些部分作出抽象,但不需要对每个部分都进行刻意抽象

序言

通过封装、继承、多态降低程序的耦合度
用设计模式使得程序更加灵活,容易修改,并且易于复用
封装:对业务方法进行封装,实现方法的复用,不论在什么地方都可以直接移植,重复使用。
继承:在以后程序的拓展时,不会影响到主代码,直接通过继承,进行实现(比方计算器加减乘除功能的基础上,添加一个开平方,只需要继承运算类,并重写运算方法就可以了,不会暴露其他功能给开发者,避免风险,也可以实现拓展)
多态:在实例化对象时使用,可以根据不同的情况进行实例化不同的对象,并赋值给父类

MVC : Model是应用对象,View是它在屏幕上的表示,Controller是定义用户界面对用户输入的响应方式。如果不使用MVC,则用户界面设计往往将这些对象混在一起,而MVC则将他们分离以提高灵活性和复用性。

面向对象设计模式体现的就是抽象的思想;类是对对象的抽象;抽象类是对类的抽象;接口是对行为的抽象。

单一职责原则: 一个类应该仅有一个引起它变化的原因(不应该把所有的功能代码都放在同一个类中)

开放-封闭原则: 软件实体(类、模块、函数等等)应该可以扩展,但不能修改

依赖倒转原则
抽象不应该依赖细节,细节应该依赖抽象(针对接口编程,而不是对实现编程)
高层模块不应该依赖低层模块。两个都应该依赖于抽象(Animal animal = new Cat() )

里氏代换原则:子类型必须能够替换掉他们的父类型

迪米特法则:如果两个类不必彼此通信,那马这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用(强调类之间的松耦合)

合成/聚合复用原则: 尽量使用合成/聚合,尽量不要使用类继承。好处是有助于你保持每个类都被封装,并被集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大物。

敏捷开发原则:不要为代码添加基于猜测的,实际不需要的功能。


简单工厂模式(解决创建对象问题)

新建一个简单工厂类,通过传入不同条件,实例化不同的对象(继承同一父类),然后多态向上转型,返回对象

此处可以考虑使用反射机制来接触分支判断带来的耦合


策略模式(针对算法: 以相同的方式调用所有的算法,减少各种算法与使用算法类的耦合)

定义:定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户

定义一个算法抽象类,多个类继承这个抽象类,实现其抽象方法(具体算法),定义一个Context类,此类通过构造函数保存某个具体的算法对象(通过构造方法传进来具体的算法对象),此类中有一个方法是调用传入参数的方法,在客户端只需要定义一个Context对象,并根据不同条件传入具体的算法对象即可。

在此基础上可以进行策略和简单工厂的结合:把简单工厂在客户端的判断移植到策略模式的Context类内进行判断,消除在客户端进行大量判断的行为

简单工厂模式和策略模式最大的不同是,简单工厂模式需要客户端认识两个类 ,策略模式则只让客户端认识一个类,降低耦合度。

在分析过程中,听到需要在不同的时间应用不同的业务规则,就可以考虑使用策略模式。


装饰模式(实质上是一个父类当成参数传入子类中,子类对其的方法进行实现的基础上,加上子类自己的方法,子类又当成参数传递给子类的子类)

定义一个抽象类,再分别定义一个类(被修饰的类)继承此类,实现其抽象方法,以及定义一个抽象类(用来修饰上一个类)继承此类(把被修饰的类当成参数传入修饰实现类),然后以不同的方式实现抽象类的抽象方法,在被修饰类的实现上进行拓展修饰。

装饰模式是在已有功能动态的添加更多功能的一种方式
当系统需要添加功能的时候,并且这个功能只是满足在某种特定情况下才会执行的特殊行为,可以考虑使用装饰模式(这样就能把核心区域和装饰区域分开了,但要注意装饰顺序)


代理模式(为其他对象提供一种代理以控制对这个对象的访问)

定义一个抽象类,两个类(具有相同的方法)去进行实现,一个为代理类,这个类代替另一个类去执行,由代理类去接收外部条件,并在代理类的方法中引用另一个类的方法(可以在构造方法的时候创建该类),实现对象的隐藏,只暴露代理类。

应用场景
远程代理,用来为一个对象在不同的地址空间提供局部代理
虚拟代理,存放实例化需要很长时间的真实对象(html网页图片加载慢,使用图片框替代)
安全代理,用来控制真实对象访问的权限
智能指引,当调用真实对象时,代理处理另外一些事


工厂方法模式(定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法是一个类的实例化延迟到其子类)

定义一个工厂接口,加减乘除各建一个具体工厂实现这个接口,在这些具体工厂的方法中,实例化要返回的对象

工厂方法与简单工厂最大的区别就是简单工厂在拓展的时候,违背了开闭原则,又保持了封装对象创建过程的优点


原型模式

类实现ICloneable接口,实现其中的Clone方法,进行此类的实例化对象进行深浅克隆,避免重复创建相同的对象


模板方法模式(定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤)

把只有小部分差异的多个类(如考试卷),相同之处提炼出来定义为一个模板类,这几个类继承这个类,并重写有差异的方法(考试答案),并向上转型,实现多态。


外观模式(为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更容易使用)

定义类,把几个组合好的子系统类在此类中实例化,客户根本不知道这几个类的存在只知道暴露出来的这个外部类

使用场合:比如经典的三层架构,在数据访问层、业务逻辑层、表现层的层与层之间建立外观模式,这样就可以为复杂的子系统提供简单的接口,降低耦合度


建造者模式(将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示)

比方在画小人的时候,先定义一个画人抽象类,由各种不同类型(高矮胖瘦,手臂长短)的人去继承他,并实现其内的抽象方法,然后再定义一个指挥者类,对传入的不同类型(此处使用多态)的人的类进行描画

使用场合:主要用于创建一些复杂的对象,这些对象内部构建间的建造顺序通常是稳定的,但对象内部的构建通常面临着复杂的变化。


观察者模式(定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所以观察者对象,使他们能够自动更新自己)

新建一个观察者抽象类,写几个子类继承该抽象类(也可以实现同一个更新接口,因为实际开发中,需要更新的类可能风马牛不相及),新建一个主题者,里面写一个方法添加多个观察者到一个集合里,此类内还要写一个方法调用这些观察者的更新方法(在主题者状态改变的情况下,实现不同实现方式)(在此基础上也可以把主题者类声明为抽象类,进行拓展)

范例: 前台通知同事,前台看到老板回来了,状态改变,同事即为观察者

使用场合: 当一个对象的改变需要同事改变其他对象的时候,而且他也不知道具体有多少对象有待改变。

一个抽象模型里面有两个方面,其中一方面依赖另一个方面,这时使用观察者模式可以将这两者封装在独立的对象中使他们各自队里的改变和复用。
让耦合的双方都依赖于抽象而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化


抽象工厂模式(提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类)

定义一个抽象工厂接口类,里面的抽象方法返回值为另外几个相关的接口类,继承并实现这些抽象方法,然后对这几个接口类进行继承实现,这样就实现了无需指定他们具体类的目的。

优缺点:抽象工厂可以很方便的在几个类中进行切换,缺点就是,如果想在增加几个相关接口实现类,需要改动的类比较多。并且如果100个类需要这个抽象工厂的实现类 ,就得声明一百次(此处可以使用IOC的依赖注入,只声明一次放进容器就行了,原理是通过反射机制,然后通过字符串确定实例化的对象,也可加载到配置文件中)


状态模式(当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类)

经常在一个方法中遇到需要根据不同的分支判断,进行不同操作,这时候可以定义一个状态抽象类(里面含抽象方法),这几个操作分别继承这个状态抽象类,并实现抽象方法,在方法里进行一个小分支的判断,else的情况下,再跳转到另一个操作实现类,以此类推,然后定义一个类,根据传入的条件,从第一个分支操作类(只写这一个)的方法开始

状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况,把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑转移简化。


适配器模式(将一个类的接口转换成客户喜欢的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作)

一个已经存在或者已经封装死的类想要调用另一个类的方法,写一个适配器类,继承该封装类,在里面实例化另一个类,并写一个调用另一个类方法的方法

系统的数据和行为都正确,但接口不符时,我们应该考虑用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配。适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况

这样只需要调用同一个接口就行了,但此模式比较适合在双方都不太容易修改的时候,一般在开发后期或者维护期比较常用。


备忘录模式(在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态了)

先定义一个需要保存的类,再定义一个备忘录类,用来保存需保持类的当前各种属性状态,在需保存类中定义几个方法(以备忘录类对象为参数,把需保存类的各个属性赋值给备忘录类),再定义一个管理者类,进行备忘录类的得到与设置(get,set方法)。游戏保存进度。

适用于功能比较复杂,但需要维护或记录属性历史的类,或许或者需要保存的属性只是众多属性中的一小部分时。缺点是,状态数据很大很多的时候,在资源消耗上,备忘录对象十分耗内存


组合模式(将对象组合成树形结构以表示‘部分-整体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性)

新建一个抽象类,定义一个枝点类继承该抽象类,并在该类内声明一个集合,实现添加、移除方法(往集合里,通过向上转型),再定义一个叶节点类继承抽象类,空实现添加、移除方法(不具备功能)。

当需求中体现部分与整体层次的结构时,以及希望用户可以忽略组合对象和单个对象的不同,同意使用组合结构中的所有对象时,就可以考虑使用组合模式。


迭代器模式(提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示)

定义一个抽象迭代器类,实现此类,对传入的集合进行遍历等各种集合操作

就是java中的Iterator,现在很多高级编程语言已经把这个模式做到语言中了

分离了集合对象 的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据


单例模式(保证一个类仅有一个实例,并提供一个访问它的全局访问点)

定义一个类,把此类的构造方法设置为私有,不允许外界创建,写一个静态方法,判断要实例化的类是否为null,不为null就创建

通常我们可以让一个全局变量使得一个对象被访问,但他不能防止你是实例化多个对象。一个最好的办法就是,让类自身负责保存他的唯一实例。这个类可以保证没有其他实例可以被创建,并且提供一个访问该实例的方法

重点:在多线程程序中,多个线程同时访问,会有可能创建多个实例的,这时需要给这个方法加一把锁。这把锁可以加在判断为null之后,实例化之前,这样可以提高性能,但必须再做一次判断为null,因为可能两个线程同时实例化,这个叫双重锁定


桥接模式(将抽象部分与它的实现部分分离,使他们都可以独立地变化)

定义两个抽象类(手机品牌,软件),各自进行多个继承与实现,把软件对象当成参数传入手机品牌,这样再添加手机品牌或软件时,就不需要改动多个类,只需要添加一个类就行了。

实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让他们独立变化,减少他们之间的耦合。

两个抽象类之间通过聚合关系(a包含b,但b并不是a的一部分)实现桥接(手机品牌和软件)


命令模式(将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作)

拿点烧烤举例,定义一个抽象命令类,把点不同烤串命令继承此类进行实现(传入厨师类,调用厨师类的一个操作方法),再定义一个服务员类(传入命令实现类),此类中含有一个集合,对传入的命令进行添加、移除,以及全部执行。

优点:较容易设计一个命令队列;在需要的情况下,可以较容易地将命令记入日志;允许接收请求的一方是否要否决请求;可以容易地实现对请求的撤销和重做;加进新的具体命令类不影响其他类;把请求一个操作的对象与知道怎么执行一个操作的对象分割开

如果不清楚一个系统是否需要命令模式,一般不要着急去实现它,事实上,在需要的时候通过重构实现这个模式并不难,只有在真正需要如撤销/回复操作等功能时,把原来的代码重构为命令模式才有意义。


职责链模式(使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止)

拿请假、加薪来举例,逐级向上,判断是否有权力进行处理,无权则通过对象的引用传递给上级接着处理。先定义一个管理者抽象类(定义一个方法,参数为自身,进行赋值,由多态进行实现),经理、总监、总经理继承这个类,对其中的抽象方法进行实现(请求条件作为参数),对这些请求进行独立的判断,成功则进行实现,失败则传递给上一级(经理->总监->总经理)

当客户提交一个请求时,请求是沿链传递直至有一个ConreteHandler对象负责处理它。


中介者模式/调停者模式(用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互)

定义一个中介者抽象类,实现它,再定义一个同事抽象类,并多种实现,通过具体中介者类,把多种同事类保存到中介者类中,并定义方法进行判断调用哪个保存进去的同事类中的方法

当系统出现多对多交互复杂的对象群时,不要急于使用中介者模式(因为此模式加大了中介者类的责任),要先反思系统在设计上是不是合理。此模式减少了各个Colleague的耦合,使得可以独立地改变和复用各个Coleague和Mediator。适合集中控制,但使用要考虑清楚。


享元模式(运用共享技术有效地支持大量细粒度的对象)

定义一个分享的抽象类,并分为要分享和不分享进行分别实现,再定义一个分享工厂类,此类内声明一个map集合,构造函数中,把要分享的类生成(也可以不在构造函数中事先生成,也可以在方法中进行判断,需要时再去生成),并存进集合里(判断集合中是否存在,不存在则实例化),再写一个方法,通过key返回map中的对象,实现共享。

如果一个应用程序使用了大量对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用;还有就是对象的大多数状态可以外部状态,如果删除对象的外部状态,那马可以用相对较小的共享对象取代很多组对象,此时可以考虑使用享元模式。


解释器模式(给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子)

定义一个文本类,存放要解释的语句,定义一个解释器抽象类,其中定义一个方法(以文本类对象为参数),对文本进行拆分,再定义一个抽象解释方法,然后进行多个实现。

如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表示为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。(正则表达式)

当有一个语言需要解释执行,并且你可将该语言的句子表示为一个抽象语法树时,可以使用解释器模式。可以很容易地改变和拓展文法,因为该模式使用类来表示文法规则,你可使用继承来改变或拓展该文法,也比较容易实现文法,因为定义抽象语法树中各个节点的类的实现大体类似,这些类都抑郁直接编写。不足:此模式为文法中的每一条规则至少定义了一个类,因此包含许多规则的文法可能难以管理和维护。建议当文法非常复杂时,使用其他的技术如语法分析程序或编译器生成器来处理。


访问者模式(表示一个作用于某对象结构中的各元素的操作。它使你可以不改变各元素的类的前提下定义作用于这些元素的新操作)

定义一个人抽象类(一个接受抽象方法,以状态实例作为参数),一个状态抽象类(一个男人反应抽象方法,以this男人作为参数,一个女人反应抽象方法,以this女人作为参数),再定义一个对象结构类(一个集合,提供方法对集合进行添加男女和调用显示调用他们的方法),通过先把男女添加进入对象结构类,再遍历调用人的接受方法(传入状态,本质调用状态里的方法)。使用双分派技术,算是最复杂的一种模式

系统有比较稳定的数据结构,又有易于变化的算法的话,使用访问者模式比较合适,因为访问者模式使得算法操作的增加变得更容易。如果系统的数据结构易于变化,进场要有新的数据对象增加进来,就不适合访问者模式了(比如,人不止有男女,而是不断添加,那就不符合开闭原则了),优点是增加新的操作很容易,因为增加新的操作就意味着增加一个新的访问者,访问者模式将有关的行为集中到一个访问者对象中。缺点就是增加新的数据结构变得困难。