设计模式对比
1.简单工厂、工厂模式和抽象工厂
简单工厂:将实现封装在Factory中;
工厂模式:将不同的实现封装在不同的Factory中;
抽象工厂:扩充产品类型,简单工厂和工厂模式针对的是一种类型的产品,而抽象工厂是针对多种相关联的产品簇,选择一个Factory,可以构建一个相互关联的产品簇。
-
简单工厂:为了实现面向接口编程,即封装和解耦。将创建对象的实现封装在Factory里面。(依赖倒置:依赖抽象,而不是依赖具体类)
一种通用的实现方式:配置文件(指定选择实现类),Factory.createApi方法读取配置文件并通过反射创建实例对象。
-
简单工厂与工厂模式的对比:工厂模式在简单工厂的基础上更进了一步,对于不同对象的具体实现,都创建一个相对应的Factory,也即具体的ConcreteCreator。
-
抽象工厂模式:对象数量的扩充,属于产品簇(一类相互关联的产品)的选择,此时一个ConcreteFactory会包含产品簇中所有产品的选择方法。如果只是针对一种产品,那么抽象工厂模式就是工厂模式,只不过是产品类型进行了扩充。
2.构建类型的模式
工厂类模式:将类的实现封装在Factory中。
建造者模式:分离不变的整体构建算法和可变的部件构造。
原型模式:克隆生成对象。使用面向接口方式,可以实现多种克隆。注意深度拷贝怎么实现。
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。注意饿汉式和懒汉式实现。最经典的的懒汉式实现是——静态内部类。
记忆方式——FBPS
-
建造者模式——分离整体构建算法和部件构造。也即分离变与不变,不变的是整体构建算法。如果在实现Builder的时候,只有创建对象的功能,而没有组装的功能,那么这个时候的Builder实现跟抽象工厂的实现是类似的。
-
原型模式——克隆生成对象。也使用了面向接口的方式,可以有多种实现克隆接口的方式。注意需要使用深度克隆。这个的应用场景?
-
单例模式——保证一个类仅有一个实例,并提供一个访问它的全局访问点。控制实例数目。注意懒汉式和饿汉式的实现。
1)恶汉模式:饿汉模式在类加载的时候就对实例进行创建,实例在整个程序周期都存在。
2)懒汉模式:方法外加synchronized;双重校验锁(改进:加volatile);静态内部类(与饿汉模式一样,也是利用了类加载机制;只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载。可以同时保证延迟加载和线程安全。)
参考Java单例模式(懒汉式、饿汉式、双检锁、静态内部类、枚举)
3.结构型模式
适配器模式:转换匹配,复用功能。需要继承Target接口,然后调用Adaptee方法实现Target接口。
桥接模式:分离抽象和实现。让抽象部分拥有实现部分的接口对象,在抽象部分就可以通过这个接口来调用具体实现部分的功能。核心本质是面向接口编程。
组合模式:统一叶子对象和组合对象。将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
装饰器模式:动态组合。动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式比生成子类更为灵活。装饰器一定要实现和组件类一致的接口,保证它们是同一个类型,并具有同一个外观,这样组合完成的装饰才能够递归的调用下去。
享元模式:分离出大量细粒度重复对象,然后放到实例池中进行共享。
外观模式:封装交互,简化调用。核心就是封装,降低耦合。
代理模式:为其它对象提供一种代理以控制对这个对象的访问。有静态代理和动态代理,动态代理包括JDK动态代理和CGLIB动态代理。
在做一个小分类:
封装降低耦合:外观模式;
功能增强:装饰器模式、代理模式;
面向接口编程:适配器模式、桥接模式、组合模式;
记忆方式:ABCDFFP
-
适配器模式——转换匹配,复用功能。
-
桥接模式——分离抽象和实现。
桥接模式还很好的体现了:多用对象组合,少用对象继承。(继承对于不同维度的实现是乘法,而组合是加法)
只要让抽象部分拥有实现部分的接口对象,这就桥接上了,在抽象部分就可以通过这个接口来调用具体实现部分的功能。
1)JDBC——DriverManager在这里起到了类似于简单工厂的功能。
基于JDBC的应用程序,使用JDBC的API,相当于是对数据库操作的抽象的扩展,算作桥接模式的抽象部分;而具体的接口实现是由驱动来完成的,驱动这边自然就相当于桥接模式的实现部分了。而桥接的方式,不再是让抽象部分持有实现部分,而是采用了类似于工厂的做法,通过 DriverManager来把抽象部分和实现部分对接起来,从而实现抽象部分和实现部分解耦。
2)广义桥接——面向抽象编程、面向接口编程
3)三层模型
-
组合模式——统一叶子对象和组合对象。将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
-
装饰器模式——动态组合。动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式比生成子类更为灵活。
装饰器是用来装饰组件的,装饰器一定要实现和组件类一致的接口,保证它们是同一个类型,并具有同一个外观,这样组合完成的装饰才能够递归的调用下去。
1)I/O流
a)InputStream就相当于装饰模式中的 Component。
b)其实FileInputStream、ObjectInputStream、StringBufferInputStream这几个对象是直接继承了InputSream,还有几个直接继承InputStream的对象,比如: ByteArrayInputStream、PipedInputStream等。这些对象相当于装饰模式中的 ConcreteComponent,是可以被装饰器装饰的对象。
c)那么FilterInputStream就相当于装饰模式中的Decorator,而它的子类 DataInputStream、BufferedInputStream、LineNumberInputStream和 PushbackInputStream就相当于装饰模式中的ConcreteDecorator了。另外 FilterInputStream和它的子类对象的构造器,都是传入组件InputStream类型,这样就完全符合前面讲述的装饰器的结构了。
-
享元模式——分离与共享。运用共享技术有效地支持大量细粒度的对象。
在享元模式中,为了创建和管理共享的享元部分,引入了享元工厂,享元工厂中一般都包含有享元对象的实例池,享元对象就是缓存在这个实例池中的。
-
外观模式——封装交互,简化调用。为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
体现了最少知识原则(迪米特法则),进行封装,降低耦合。
可以将Facade定义成接口,然后进一步封装Facade实现中对子系统的调用。
-
代理模式——为其它对象提供一种代理以控制对这个对象的访问。
1)Java静态代理:这种实现方式有一个较大的缺点,就是如果Subject接口发生变化,那么代理类和具体的目标实现都要变化,不是很灵活。
2)Java动态代理:态代理实现的时候,虽然Subject接口上定义了很多方法,但是动态代理类始终只有一个invoke方法。这样当Subject接口发生变化的时候,动态代理的接口就不需要跟着变化了。包括两种方式:JDK动态代理+CGLIB动态代理。
JDK动态代理需要使用InvocationHandler以及生成代理类时需要指定接口;
CGLIB动态代理不需要依赖接口,原理是对指定的委托类生成一个子类并重写其中业务方法来实现代理。代理类对象是由 Enhancer 类创建的。需要使用MethodInterceptor+Enhancer。使用Enhancer生成代理类,代理类继承委托类,使用MethodInterceptor.intercept对代理类所有方法调用进行拦截,然后转而调用父类的原始方法调用。
4.行为型
解释器模式Interpreter:解释指定文法的句子。
模板方法模式Template Method:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。
责任链模式Chain of Responsibility:分离职责,动态组合。对象组成链。避免请求的发送者和接收者之间的耦合关系。
命令模式Command:把请求封装成为对象,并定义了统一的执行操作的接口,这个命令对象可以被存储、转发、记录、处理、撤销等。
迭代器模式Iterator:提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。
中介者模式Mediator:用一个中介对象来封装一系列的对象交互。与主板的功能非常类似。
备忘录模式Memento:在不破坏封装的前提下,保存和恢复内部状态。
观察者模式Observer:当目标对象的状态发送变化时,依赖于它的对象都被通知并执行相应操作。
状态模式State:根据状态来分离和选择行为。在其内部状态改变时改变它的行为。
策略模式Strategy:定义一系列算法,并且它们可以相互替换。
访问者模式Visitor:预留通路,回调实现。可以在不改变类结构的前提下定义操作这些类的新操作。
记忆方式:
ITCCIMMOSSV
IT男要经常喝CC,I'm就是经常吃moss(地衣)V维生素含量高。
解释器相关:
解释器模式——按照文法解释句子
访问者模式——解释器可以按照访问者模式实现
组合模式——抽象语法树的结构就是组合模式
回调相关:
命令模式
模板方法模式
访问者模式
策略模式与模板方法模式组合(前提:所有方法骨架相同):
策略模式定义整个骨架
模板方法模式定义所有算法的骨架
封装
迭代器模式:提供统一的访问方式,隐藏内部表示
中介者模式:封装内部对象交互
外观模式:向外提供简化的接口调用
工厂类模式:封装类的实现
-
解释器——给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。跟组合模式强关联。
-
模板方法——定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。与回调、方法引用及命令模式强相关。
-
责任链模式——分离职责,动态组合。使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
-
命令模式——命令模式的关键之处就是把请求封装成为对象,也就是命令对象,并定义了统一的执行操作的接口,这个命令对象可以被存储、转发、记录、处理、撤销等,整个命令模式都是围绕这个对象在进行。
-
迭代器模式——提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。
-
中介者模式——封装交互。用一个中介对象来封装一系列的对象交互。中介者使得各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立的改变它们之间的交互。与主板封装了CPU、内存、IO、外设之间的交互本质上是一模一样。与Facade强相关。
-
备忘录模式——本质是:保存和恢复内部状态。在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。可结合原型模式——在原发器对象创建备忘录对象的时候,如果原发器对象中全部或者大部分的状态都需要保存,一个简洁的方式就是直接克隆一个原发器对象。
-
观察者模式(发布-订阅模式)——本质是触发联动。定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。在观察者模式中,观察者和目标是单向依赖的,只有观察者依赖于目标,而目标是不会依赖于观察者的。它们之间联系的主动权掌握在目标手中,只有目标知道什么时候需要通知观察者,在整个过程中,观察者始终是被动的,被动的等待目标的通知,等待目标传值给它。
-
状态模式——本质:根据状态来分离和选择行为。运行一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。状态模式的功能就是分离状态的行为,通过维护状态的变化,来调用不同状态对应的不同功能。
状态决定行为。状态模式的结构和策略模式是一样的,但是它们的目的和本质是完全不一样的。
1)状态模式的平行性指的是各个状态的行为所处的层次是一样的,相互是独立的、没有关联的,是根据不同的状态来决定到底走平行线的哪一条,行为是不同的。相互之间不可替换。
2)策略模式的平等性强调的是可替换性,是针对同一行为的不同描述或实现。
-
策略模式——分离算法,选择实现。定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。使得算法可独立于使用它的客户而变化。与模板模式相关,若算法的骨架一样,则抽象类就成了模板方法模式的模板类。
-
访问者模式——本质是预留通路,回调实现。表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。与组合模式相关。
重要的就是依靠通用方法,访问者这边说要去访问,就提供一个访问的方法,如 visit方法;而对象那边说,好的,我接受你的访问,提供一个接受访问的方法,如accept方法。这两个方法并不代表任何具体的功能,只是构成一个调用的通路。在 accept方法里面,回调visit的方法,从而回调到访问者的具体实现上,而这个访问者的具体实现的方法才是要添加的新的功能。
5.不同维度的联系(纵向和横向拓展)
5.1 整体三大类
记忆方式
- FBPS
- ABCDFFP
- ITCCIMMOSSV
IT男要经常喝CC,I'm就是经常吃moss(地衣)V维生素含量高。
-
创建型:与对象创建有关
- 简单工厂:将实现封装在Factory中;
- 工厂模式Factory Method:将不同的实现封装在不同的Factory中;
- 抽象工厂Abstract Factory:扩充产品类型,简单工厂和工厂模式针对的是一种类型的产品,而抽象工厂是针对多种相关联的产品簇,选择一个Factory,可以构建一个相互关联的产品簇。
- 建造者模式Builder:分离不变的整体构建算法和可变的部件构造。
- 原型模式Prototype:克隆生成对象。使用面向接口方式,可以实现多种克隆。注意深度拷贝怎么实现。
- 单例模式Singleton:保证一个类仅有一个实例,并提供一个访问它的全局访问点。注意饿汉式和懒汉式实现。最经典的的懒汉式实现是——静态内部类。
-
结构型:处理类或对象的组合
- 适配器模式Adapter:转换匹配,复用功能。需要继承Target接口,然后调用Adaptee方法实现Target接口。
- 桥接模式Bridge:分离抽象和实现。让抽象部分拥有实现部分的接口对象,在抽象部分就可以通过这个接口来调用具体实现部分的功能。核心本质是面向接口编程。
- 组合模式Composite:统一叶子对象和组合对象。将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
- 装饰器模式Decorator:动态组合。动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式比生成子类更为灵活。装饰器一定要实现和组件类一致的接口,保证它们是同一个类型,并具有同一个外观,这样组合完成的装饰才能够递归的调用下去。
- 享元模式Flyweight:分离出大量细粒度重复对象,然后放到实例池中进行共享。
- 外观模式Facade:封装交互,简化调用。核心就是封装,降低耦合。
- 代理模式Proxy:为其它对象提供一种代理以控制对这个对象的访问。有静态代理和动态代理,动态代理包括JDK动态代理和CGLIB动态代理。
-
行为型:对类或对象怎样交互和怎样分配职责进行描述
- 解释器模式Interpreter:解释指定文法的句子。
- 模板方法模式Template Method:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。
- 责任链模式Chain of Responsibility:分离职责,动态组合。对象组成链。避免请求的发送者和接收者之间的耦合关系。
- 命令模式Command:把请求封装成为对象,并定义了统一的执行操作的接口,这个命令对象可以被存储、转发、记录、处理、撤销等。
- 迭代器模式Iterator:提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。
- 中介者模式Mediator:用一个中介对象来封装一系列的对象交互。与主板的功能非常类似。
- 备忘录模式Memento:在不破坏封装的前提下,保存和恢复内部状态。
- 观察者模式Observer:当目标对象的状态发送变化时,依赖于它的对象都被通知并执行相应操作。
- 状态模式State:根据状态来分离和选择行为。在其内部状态改变时改变它的行为。
- 策略模式Strategy:定义一系列算法,并且它们可以相互替换。
- 访问者模式Visitor:预留通路,回调实现。可以在不改变类结构的前提下定义操作这些类的新操作
5.2 设计模式原则
5.2.1 开闭原则(Open Closed Principle,OCP)
在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
要实现对扩展开放,对修改关闭,即遵循开闭原则,需要对系统进行抽象化设计,抽象可以基于抽象类或者接口。一般来说需要做到几点:
- 1)通过接口或者抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法,也就是扩展必须添加具体实现而不是改变具体的方法。
- 2)参数类型、引用对象尽量使用接口或者抽象类,而不是实现类,这样就能尽量保证抽象层是稳定的。
- 3)一般抽象模块设计完成(例如接口的方法已经敲定),不允许修改接口或者抽象方法的定义。
开闭原则的核心:面向接口或者面向抽象编程,这样基于里氏替换原则,可以方便地进行扩展而无需修改现有代码。
所以符合面向接口编程或者面向抽象编程的设计模式都符合开闭原则:
- 避免对具体创建的依赖
工厂类模式+Prototype实现间接地创建对象——Factory Method针对不同实现可方便扩展不同工厂类,因为工厂类和实现类都是扩展自各自的接口。(可避免受特定实现约束而不是特定接口约束。) - 避免对特殊操作的依赖
责任链模式、命令模式——所有责任链上的对象都实现相同的接口,可以方便地进行功能扩展和组合。命令模式Command把请求封装成为对象,并定义了统一的执行操作的接口,这个命令对象可以被存储、转发、记录、处理、撤销等。(可避免对特殊操作的依赖,可以方便地改变响应请求的方法。) - 避免对硬件和软件平台的依赖
抽象工厂模式
桥接模式(JDBC)——不同的实现都基于统一的接口,而使用方只用针对提供的接口编程即可。 - 避免算法依赖导致不易更改
建造者模式
迭代器模式
策略模式——所有算法实现相同的接口,可以方便地新增算法
模板方法模式
访问者模式——所有访问者都实现相同的接口,可以方便地新增不同功能的访问者类
- 装饰器模式——不同的装饰器都要实现和组件类一致的接口,这样可以很方便地添加和组合想要的功能
- 代理模式——代理对象需要实现真实对象的接口,这样代理对象就可对真实对象进行功能增
- 状态模式——所有状态都实现相同的接口,可以方便地进行状态的添加
5.2.1 里氏代换原则(Liskov Substitution Principle,LSP)
所有引用基类的地方必须能透明地使用其子类的对象,也可以简单理解为任何基类可以出现的地方,子类一定可以出现。
只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对"开-闭"原则的补充。实现"开-闭"原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
里氏代换原则是实现开闭原则的基础,它告诉我们在设计程序的时候进可能使用基类进行对象的定义和引用,在运行时再决定基类的具体子类型。
5.2.1 依赖倒转原则(Dependency Inversion Principle,DIP)
程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。为了确保该原则的应用,一个具体类应当只实现接口或抽象类中声明过的方法,而不要给出多余的方法,否则将无法调用到在子类中增加的新方法。在引入抽象层后,系统将具有很好的灵活性,在程序中尽量使用抽象层进行编程,而将具体类写在配置文件中,这样一来,如果系统行为发生变化,只需要对抽象层进行扩展,并修改配置文件,而无须修改原有系统的源代码,在不修改的情况下来扩展系统的功能,满足开闭原则的要求。
依赖倒转原则的注意事项:
- 高层模块不应该依赖低层模块,高层模块和低层模块都应该依赖于抽象。
- 抽象不应该依赖于具体,具体应该依赖于抽象。
例如:
- Spring的IOC。
针对抽象层编程,将具体类的对象通过依赖注入(DependencyInjection, DI)的方式注入到其他对象中,依赖注入是指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。常用的注入方式有三种,分别是:构造注入,设值注入(Setter注入)和接口注入。 - 面向接口(抽象)编程
1)每个具体的类都应该有其接口或者基类,或者两者都具备。
2)类中的引用对象应该是接口或者基类。
3)任何具体类都不应该派生出子类。
4)尽量不要覆写基类中的方法。
5)结合里氏代换原则使用。
5.2.2 单一职责原则(Single Responsibility Principle, SRP)
一个类或者模块应该有且只有一个改变的原因。如果一个类承担的职责过多,就等于把这些职责耦合在一起了。此原则的核心就是解耦和增强内聚性。
一个类不能做太多的东西。在软件系统中,一个类(一个模块、或者一个方法)承担的职责越多,那么其被复用的可能性就会越低。
项目分层:数据库层、service层、controller层。
5.2.3 接口隔离原则(Interface Segregation Principle,ISP)
户端不应该依赖它不需要的接口,类间的依赖关系应该建立在最小的接口上。简单来说就是建立单一的接口,不要建立臃肿庞大的接口。也就是接口尽量细化,同时接口中的方法尽量少。
接口隔离原则的规范:
- 使用接口隔离原则前首先需要满足单一职责原则。
- 接口需要高内聚,也就是提高接口、类、模块的处理能力,少对外发布public的方法。
- 定制服务,就是单独为一个个体提供优良的服务,简单来说就是拆分接口,对特定接口进行定制。
- 接口设计是有限度的,接口的设计粒度越小,系统越灵活,但是值得注意不能过小,否则变成"字节码编程"。
符合ISP原则的设计模式:
- 外观模式Facade
5.2.4 合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)
尽量使用合成/聚合,而不是通过继承达到复用的目的。
合成/聚合复用原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向内部持有的这些对象的委派达到复用已有功能的目的,而不是通过继承来获得已有的功能。
聚合(Aggregate)的概念:
- 聚合表示一种弱的"拥有"关系,一般表现为松散的整体和部分的关系,其实,所谓整体和部分也可以是完全不相关的。例如A对象持有B对象,B对象并不是A对象的一部分,也就是B对象的生命周期是B对象自身管理,和A对象不相关。
合成(Composite)的概念:
- 合成表示一种强的"拥有"关系,一般表现为严格的整体和部分的关系,部分和整体的生命周期是一样的。
继承复用破坏包装,因为继承将基类的实现细节暴露给派生类,基类的内部细节通常对子类来说是可见的,这种复用也称为"白箱复用"。这里有一个明显的问题是:派生类继承自基类,如果基类的实现发生改变,将会影响到所有派生类的实现;如果从基类继承而来的实现是静态的,不可能在运行时发生改变,不够灵活。
由于合成或聚合关系可以将已有的对象,一般叫成员对象,纳入到新对象中,使之成为新对象的一部分,因此新对象可以调用已有对象的功能,这样做可以使得成员对象的内部实现细节对于新对象不可见,所以这种复用又称为"黑箱"复用,相对继承关系而言,其耦合度相对较低,成员对象的变化对新对象的影响不大,可以在新对象中根据实际需要有选择性地调用成员对象的操作;合成/聚合复用可以在运行时动态进行,新对象可以动态地引用与成员对象类型相同的其他对象。
一般情况下,只有明确知道派生类和基类满IS A的时候才选用继承,当满足HAS A或者不能判断的情况下应该选用合成/聚合。
符合合成/聚合复用原则的设计模式:
- 适配器模式——Adapter持有Adaptee对象
- 桥接模式——RefineAbstraction持有Implementor的具体实现;DriveManager持有驱动的具体实现;逻辑层持有数据结构层的具体实现
- 代理模式——代理类持有真实对象
- 命令模式——Invoker持有Command对象,Command持有Receiver对象
- 迭代器模式——迭代器持有聚合对象
- 中介者模式——中介者持有相互交互的对象
- 观察者模式——目标持有观察者对象集合
GoF中说明的模式:
- 桥接模式
- 责任链模式
- 组合模式
- 装饰器模式
- 观察者模式
- 策略模式
5.2.5 最少知识原则(Least Knowledge Principle,LKP)或者迪米特法则(Law of Demeter,LOD)
一个软件实体应当尽可能少地与其他实体发生相互作用。每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。迪米特法则的初衷在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。迪米特法则不希望类之间建立直接的联系。如果真的有需要建立联系,也希望能通过它的友元类(中间类或者跳转类)来转达。
迪米特法则的规则:
- Only talk to your immediate friends(只与直接的朋友通讯),一个对象的"朋友"包括他本身(this)、它持有的成员对象、入参对象、它所创建的对象。
- 尽量少发布public的变量和方法,一旦公开的属性和方法越多,修改的时候影响的范围越大。
- "是自己的就是自己的",如果一个方法放在本类中,既不产生新的类间依赖,也不造成负面的影响,那么此方法就应该放在本类中。
迪米特法则的核心观念就是类间解耦,也就降低类之间的耦合,只有类处于弱耦合状态,类的复用率才会提高。所谓降低类间耦合,实际上就是尽量减少对象之间的交互,如果两个对象之间不必彼此直接通信,那么这两个对象就不应当发生任何直接的相互作用,如果其中的一个对象需要调用另一个对象的某一个方法的话,可以通过第三者转发这个调用。简言之,就是通过引入一个合理的第三者来降低现有对象之间的耦合度。但是这样会引发一个问题,有可能产生大量的中间类或者跳转类,导致系统的复杂性提高,可维护性降低。
符合该原则的社模式:
- 中介者模式Mediator
- 外观模式Facade
- 隐藏对象表示、保存、定位或实现,避免依赖
抽象工厂
桥接模式
备忘录模式
代理模式 - 避免紧耦合
抽象工厂
命令模式
外观模式
中介者模式
观察者模式
责任链模式
5.3 其他分类
不能方便地对类进行修改:
- 适配器模式
- 装饰器模式
- 访问者模式
5.4 GoF对设计模式用途的分类
记忆法总结
- FBPS
- ABCDFFP
- ITCCIMMOSSV
IT男要经常喝CC,I'm就是经常吃moss(地衣)V维生素含量高。 - 设计原则:OLD IS CL——成龙老了