SOLID
设计模式的六大原则有:
Single Responsibility Principle:单一职责原则
Open Closed Principle:开闭原则
Liskov Substitution Principle:里氏替换原则
Law of Demeter:迪米特法则
Interface Segregation Principle:接口隔离原则
Dependence Inversion Principle:依赖倒置原则
把这六个原则的首字母联合起来( L 算做一个)就是 SOLID (solid,稳定的),其代表的含义就是这六个原则结合使用的好处:建立稳定、灵活、健壮的设计。

单一职责原则
单一职责原则的英文名称是Single Responsibility Principle,简称是SRP。 SRP原则的解释是:There should never be more than one reason for a class to change。定义很简单,即不能存在多于一个导致类变更的原因。简单的说就是一个类只负责一项职责。 在软件设计中,秉承着“高内聚,低耦合”的思想,让一个类仅负责一项职责,如果一个类有多于一项的职责,那么就代表这个类耦合性变高了,这些职责耦合在了一起,这是比较脆弱的设计。因为一旦某一项职责发生了改变,需要去更改代码,那么有可能会引起其他职责改变。所谓牵一发而动全身,这显然是我们所不愿意看到的,所以我们会把这个类分拆开来,由两个类来分别维护这两个职责,这样当一个职责发生改变,需要修改时,不会影响到另一个职责。
当然,这个原则不仅仅适用于类,对于接口和方法也适用,即一个接口/方法,只负责一件事,这样的话,接口就会变得简单,方法中的代码也会更少,易读,便于维护。
事实上,由于一些其他的因素影响,类的单一职责在项目中是很难保证的。通常,接口和方法的单一职责更容易实现。
单一原则的好处:
降低了类的复杂度。一个类只负责一项职责比负责多项职责要简单得多。
提高了代码的可读性。一个类简单了,可读性自然就提高了。
提高了系统的可维护性。代码的可读性高了,并且修改一项职责对其他职责影响降低了,可维护性自然就提高了。
变更引起的风险变低了。单一职责最大的优点就是修改一个功能,对其他功能的影响显著降低。
里氏替换原则
里氏替换原则的定义如下:
Functions that use use pointers or references to base classes must be able to use objects of derived classes without knowing it.
翻译过来就是,所有引用基类的地方必须能透明地使用其子类的对象。
里氏替换原则的意思是,所有基类在的地方,都可以换成子类,程序还可以正常运行。这个原则是与面向对象语言的 继承 特性密切相关的。
里氏替换原则包含以下4层含义:
子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法。
子类中可以增加自己特有的方法。
当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法。在我们做系统设计时,经常会设计接口或抽象类,然后由子类来实现抽象方法,这里使用的其实就是里氏替换原则。子类可以实现父类的抽象方法很好理解,事实上,子类也必须完全实现父类的抽象方法,哪怕写一个空方法,否则会编译报错。里氏替换原则的关键点在于不能覆盖父类的非抽象方法。父类中凡是已经实现好的方法,实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些规范,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。而里氏替换原则就是表达了这一层含义。因此,可以看到, LSP 原则最重要的一点就是:避免子类重写父类中已经实现的方法。这就是 LSP 原则的本质。一般来讲,程序中父类大多是抽象类,因为父类只是一个框架,具体功能还需要子类来实现。因此很少直接去 new 一个父类。而如果出现这种情况,那么就说明父类中实现的代码已经很好了,子类只需要对其进行扩展就会,尽量避免对其已经实现的方法再去重写。
依赖倒置原则
依赖倒置原则的原始定义是这样的:
High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions.
翻译一下:
高层模块不应该依赖底层模块,两者都应该依赖其抽象。
抽象不应该依赖细节。
细节应该依赖抽象。
在Java语言中的表现就是:
模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。 接口或抽象类不依赖于实现类。实现类依赖于接口或抽象类。每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备 变量的表面类型尽量是接口或者是抽象类 任何类都不应该从具体类派生 尽量不要覆写基类的方法 结合里氏替换原则使用 以上的规则通俗说就是:接口负责定义public属性和方法,并且声明与其他对象的依赖关系,抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化。
接口隔离原则
首先声明,该原则中的接口,是一个泛泛而言的接口,不仅仅指Java中的接口,还包括其中的抽象类。
首先,给出该原则的定义,该原则有两个定义:
Clients should not be forced to depend upon interfaces that they don`t use.
客户端不应该依赖它不需要的接口。
The dependency of one class to another one should depend on the smallest possible.
类间的依赖关系应该建立在最小的接口上。
这是什么意思呢,这是让我们把接口进行细分。举个例子,如果一个类实现一个接口,但这个接口中有它不需要的方法,那么就需要把这个接口拆分,把它需要的方法提取出来,组成一个新的接口让这个类去实现,这就是接口隔离原则。简而言之,就是说,接口中的所有方法对其实现的子类都是有用的。否则,就将接口继续细分。
看起来,该原则与单一职责原则很相像。确实很像,二者都是强调要将接口进行细分,只不过分的方式不同。单一职责原则是按照 职责 进行划分接口的;而接口隔离原则则是按照实现类对方法的使用来划分的。可以说,接口隔离原则更细一些。
要想完美地实现该原则,基本上就需要每个实现类都有一个专用的接口。但实际开发中,这样显然是不可能的,而且,这样很容易违背单一职责原则(可能出现同一个职责分成了好几个接口的情况),因此我们能做的就是尽量细分。
该原则主要强调两点:
接口要尽量小
接口隔离原则拆分接口时,首先必须要满足单一职责原则(SRP)
接口要高内聚
高内聚就是提高接口、类、模块的处理能力,减少对外的交互。具体就是,要求在接口中尽量少公布public方法,接口是对外的承诺,承诺越少对系统的开发越有利,变更的风险也就越少,同时也有利于降低成本。 到了这里,我们对依赖倒置原则的“依赖”就很好理解了,但是什么是“倒置”呢。是这样子的,刚开始按照正常人的一般思维方式,我想吃香蕉就是吃香蕉,想吃苹果就吃苹果,编程也是这样,都是按照面向实现的思维方式来设计。而现在要倒置思维,提取公共的抽象,面向接口(抽象类)编程。不再依赖于具体实现了,而是依赖于接口或抽象类,这就是依赖的思维方式“倒置”了。
与单一职责原则的区别
到了这里,有些人可能觉得接口隔离原则与单一职责原则很相似,其实不然。
第一,单一职责原则注重的是职责;而接口隔离原则注重对接口依赖的隔离。
第二,单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口,主要针对抽象,针对程序整体框架的构建。
注意事项
原则是前人经验的总结,在软件设计中具有一定的指导作用,但是不能完全照搬这些原则。对于接口隔离原则来说,接口尽量小,但是也要有限度。对接口进行细化可以提高程序设计灵活性是不争的事实,但是如果过小,则会造成接口数量过多,使设计复杂化,所以一定要适度。
迪米特法则
迪米特法则也叫最少知识原则(Least Knowledge Principle, LKP)
虽然名称不同,但都是同一个意思:一个对象应该对其他对象有最少的了解。
迪米特法则要求类”羞涩”一点,尽量不要对外公布太多的public方法和非静态的public变量,尽量内敛,多使用private、package-private、protected等访问权限。
实现:
1. 优先考虑将一个类设置成不变类
2. 尽量降低一个类的访问权限
3. 谨慎使用Serializable
4. 尽量降低成员的访问权限
这个原则也没什么需要多讲的,调用者只需要知道被调用者公开的方法就好了,至于它内部是怎么实现的或是有其他别的方法,调用者并不关心,调用者只关心它需要用的。反而,如果被调用者暴露太多不需要暴露的属性或方法,那么就可能导致调用者滥用其中的方法,或是引起一些其他不必要的麻烦。
开闭原则
开闭原则所有设计模式原则中,最基础的那个原则。首先,还是先来看一下它的定义:
Software entities like classes, modules and functions should be open for extension but closed for modifications.
翻译过来就是:一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭。
开闭原则之前也提到过,在 LSP 中,我们说,要避免子类重写父类中已经实现的方法。这个时候,继承父类就是对其进行扩展,但没有进行修改。这就是开闭原则一个很好的体现。
那是不是开闭原则与 LSP 原则混淆了呢?并不是,LSP原则强调的是基类与子类的关系,只是其中的一种实现方式用到了开闭原则而已。开闭原则是最基础的设计原则,其它的五个设计原则都是开闭原则的具体形态,也就是说其它的五个设计原则是指导设计的工具和方法,而开闭原则才是其精神领袖。依照java语言的称谓,开闭原则是抽象类,而其它的五个原则是具体的实现类。在面向对象的设计中,所有的逻辑都是从原子逻辑组合而来,不是在一个类中独立实现一个业务逻辑。只有这样的代码才可以复用,粒度越小,被复用的可能性越大。那为什么要复用呢?减少代码的重复,避免相同的逻辑分散在多个角落,减少维护人员的工作量。那怎么才能提高复用率呢?缩小逻辑粒度,直到一个逻辑不可以分为止。
如何使用开闭原则
第一:抽象约束
抽象是对一组事物的通用描述,没有具体的实现,也就表示它可以有非常多的可能性,可以跟随需求的变化而变化。因此,通过接口或抽象类可以约束一组可能变化的行为,并且能够实现对扩展开放,其包含三层含义: 通过接口或抽象类约束扩散,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法。 参数类型,引用对象尽量使用接口或抽象类,而不是实现类,这主要是实现里氏替换原则的一个要求 抽象层尽量保持稳定,一旦确定就不要修改
第二:元数据(metadata)控件模块行为
编程是一个很苦很累的活,那怎么才能减轻压力呢?答案是尽量使用元数据来控制程序的行为,减少重复开发。什么是元数据?用来描述环境和数据的数据,通俗的说就是配置参数,参数可以从文件中获得,也可以从数据库中获得。
第三:制定项目章程
在一个团队中,建立项目章程是非常重要的,因为章程是所有人员都必须遵守的约定,对项目来说,约定优于配置。这比通过接口或抽象类进行约束效率更高,而扩展性一点也没有减少。
第四:封装变化 对变化封装包含两层含义: (1)将相同的变化封装到一个接口或抽象类中 (2)将不同的变化封装到不同的接口或抽象类中,不应该有两个不同的变化出现在同一个接口或抽象类中。 封装变化,也就是受保护的变化,找出预计有变化或不稳定的点,我们为这些变化点创建稳定的接口。
总结
总而言之,这六个设计原则是以后学习设计模式的基础,它们的共同目的就是SOLID——建立稳定、灵活、健壮的设计。但是原则归原则,有时候由于种种原因,这些条条框框是不得不去打破的,一味地遵循它是不会有好果子吃的(就像接口隔离原则,不可能创建那么多的接口)。因此我们应该正确使用这些原则,主要目的还是为了我们软件的稳定性、灵活性、健壮性和可维护性。
JAVA设计模式总结
一、什么是设计模式
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因.
二、设计模式的三个分类
创建型模式:对象实例化的模式,创建型模式用于解耦对象的实例化过程。
结构型模式:把类或对象结合在一起形成一个更大的结构。
行为型模式:类和对象如何交互,及划分责任和算法。

一、Singleton,单例模式:保证一个类只有一个实例,并提供一个访问它的全局访问点
二、Abstract Factory,抽象工厂:提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们的具体类。 三、Factory Method,工厂方法:定义一个用于创建对象的接口,让子类决定实例化哪一个类,Factory Method使一个类的实例化延迟到了子类。
四、Builder,建造模式:将一个复杂对象的构建与他的表示相分离,使得同样的构建过程可以创建不同的表示。
五、Prototype,原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型来创建新的对象。
行为型有:
六、Iterator,迭代器模式:提供一个方法顺序访问一个聚合对象的各个元素,而又不需要暴露该对象的内部表示。
七、Observer,观察者模式:定义对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知自动更新。
八、Template Method,模板方法:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,TemplateMethod使得子类可以不改变一个算法的结构即可以重定义该算法得某些特定步骤。
九、Command,命令模式:将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队和记录请求日志,以及支持可撤销的操作。
十、State,状态模式:允许对象在其内部状态改变时改变他的行为。对象看起来似乎改变了他的类。
十一、Strategy,策略模式:定义一系列的算法,把他们一个个封装起来,并使他们可以互相替换,本模式使得算法可以独立于使用它们的客户。
十二、China of Responsibility,职责链模式:使多个对象都有机会处理请求,从而避免请求的送发者和接收者之间的耦合关系
十三、Mediator,中介者模式:用一个中介对象封装一些列的对象交互。
十四、Visitor,访问者模式:表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素类的前提下定义作用于这个元素的新操作。
十五、Interpreter,解释器模式:给定一个语言,定义他的文法的一个表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
十六、Memento,备忘录模式:在不破坏对象的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
结构型有:
十七、Composite,组合模式:将对象组合成树形结构以表示部分整体的关系,Composite使得用户对单个对象和组合对象的使用具有一致性。
十八、Facade,外观模式:为子系统中的一组接口提供一致的界面,facade提供了一高层接口,这个接口使得子系统更容易使用。
十九、Proxy,代理模式:为其他对象提供一种代理以控制对这个对象的访问
二十、Adapter,适配器模式:将一类的接口转换成客户希望的另外一个接口,Adapter模式使得原本由于接口不兼容而不能一起工作那些类可以一起工作。
二十一、Decrator,装饰模式:动态地给一个对象增加一些额外的职责,就增加的功能来说,Decorator模式相比生成子类更加灵活。
二十二、Bridge,桥接模式:将抽象部分与它的实现部分相分离,使他们可以独立的变化。
二十三、Flyweight,享元模式:运用共享技术有效地支持大量细粒度的对象。
三、浅谈23种设计模式
1.单例模式
单例模式,它的定义就是确保某一个类只有一个实例,并且提供一个全局访问点。
单例模式具备典型的3个特点:
1、只有一个实例。
2、自我实例化。
3、提供全局访问点。
因此当系统中只需要一个实例对象或者系统中只允许一个公共访问点,除了这个公共访问点外,不能通过其他访问点访问该实例时,可以使用单例模式。 单例模式的主要优点就是节约系统资源、提高了系统效率,同时也能够严格控制客户对它的访问。也许就是因为系统中只有一个实例,这样就导致了单例类的职责过重,违背了“单一职责原则”,同时也没有抽象类,所以扩展起来有一定的困难。
2.工厂方法模式
作为抽象工厂模式的孪生兄弟,工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个,也就是说工厂方法模式让实例化推迟到子类。工厂方法模式非常符合“开闭原则”,当需要增加一个新的产品时,我们只需要增加一个具体的产品类和与之对应的具体工厂即可,无须修改原有系统。同时在工厂方法模式中用户只需要知道生产产品的具体工厂即可,无须关系产品的创建过程,甚至连具体的产品类名称都不需要知道。虽然他很好的符合了“开闭原则”,但是由于每新增一个新产品时就需要增加两个类,这样势必会导致系统的复杂度增加。
3.抽象工厂模式
所谓抽象工厂模式就是提供一个接口,用于创建相关或者依赖对象的家族,而不需要明确指定具体类。他允许客户端使用抽象的接口来创建一组相关的产品,而不需要关系实际产出的具体产品是什么。这样一来,客户就可以从具体的产品中被解耦。它的优点是隔离了具体类的生成,使得客户端不需要知道什么被创建了,而缺点就在于新增新的行为会比较麻烦,因为当添加一个新的产品对象时,需要更加需要更改接口及其下所有子类。
4.建造者模式
对于建造者模式而已,它主要是将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。适用于那些产品对象的内部结构比较复杂。建造者模式将复杂产品的构建过程封装分解在不同的方法中,使得创建过程非常清晰,能够让我们更加精确的控制复杂产品对象的创建过程,同时它隔离了复杂产品对象的创建和使用,使得相同的创建过程能够创建不同的产品。但是如果某个产品的内部结构过于复杂,将会导致整个系统变得非常庞大,不利于控制,同时若几个产品之间存在较大的差异,则不适用建造者模式,毕竟这个世界上存在相同点大的两个产品并不是很多,所以它的使用范围有限。
5.原型模式
在我们应用程序可能有某些对象的结构比较复杂,但是我们又需要频繁的使用它们,如果这个时候我们来不断的新建这个对象势必会大大损耗系统内存的,这个时候我们需要使用原型模式来对这个结构复杂又要频繁使用的对象进行克隆。所以原型模式就是用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。它主要应用与那些创建新对象的成本过大时。它的主要优点就是简化了新对象的创建过程,提高了效率,同时原型模式提供了简化的创建结构。
模式结构 原型模式包含如下角色: Prototype:抽象原型类 ConcretePrototype:具体原型类 Client:客户类
6.适配器模式
在我们的应用程序中我们可能需要将两个不同接口的类来进行通信,在不修改这两个的前提下我们可能会需要某个中间件来完成这个衔接的过程。这个中间件就是适配器。所谓适配器模式就是将一个类的接口,转换成客户期望的另一个接口。它可以让原本两个不兼容的接口能够无缝完成对接。作为中间件的适配器将目标类和适配者解耦,增加了类的透明性和可复用性。
适配器模式包含如下角色:
Target:目标抽象类
Adapter:适配器类
Adaptee:适配者类
Client:客户类
7.桥接模式
如果说某个系统能够从多个角度来进行分类,且每一种分类都可能会变化,那么我们需要做的就是讲这多个角度分离出来,使得他们能独立变化,减少他们之间的耦合,这个分离过程就使用了桥接模式。所谓桥接模式就是讲抽象部分和实现部分隔离开来,使得他们能够独立变化。桥接模式将继承关系转化成关联关系,封装了变化,完成了解耦,减少了系统中类的数量,也减少了代码量。
桥接模式包含如下角色:
Abstraction:抽象类
RefinedAbstraction:扩充抽象类
Implementor:实现类接口
ConcreteImplementor:具体实现类
8.组合模式
组合模式组合多个对象形成树形结构以表示“整体-部分”的结构层次。它定义了如何将容器对象和叶子对象进行递归组合,使得客户在使用的过程中无须进行区分,可以对他们进行一致的处理。在使用组合模式中需要注意一点也是组合模式最关键的地方:叶子对象和组合对象实现相同的接口。这就是组合模式能够将叶子节点和对象节点进行一致处理的原因。虽然组合模式能够清晰地定义分层次的复杂对象,也使得增加新构件也更容易,但是这样就导致了系统的设计变得更加抽象,如果系统的业务规则比较复杂的话,使用组合模式就有一定的挑战了。
模式结构 组合模式包含如下角色:
Component: 抽象构件
Leaf: 叶子构件
Composite: 容器构件
Client: 客户类
9.装饰模式
我们可以通过继承和组合的方式来给一个对象添加行为,虽然使用继承能够很好拥有父类的行为,但是它存在几个缺陷:一、对象之间的关系复杂的话,系统变得复杂不利于维护。二、容易产生“类爆炸”现象。三、是静态的。在这里我们可以通过使用装饰者模式来解决这个问题。装饰者模式,动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更加有弹性的替代方案。虽然装饰者模式能够动态将责任附加到对象上,但是他会产生许多的细小对象,增加了系统的复杂度。
模式结构 装饰模式包含如下角色:
Component: 抽象构件
ConcreteComponent: 具体构件
Decorator: 抽象装饰类
ConcreteDecorator: 具体装饰类
10.外观模式
我们都知道类与类之间的耦合越低,那么可复用性就越好,如果两个类不必彼此通信,那么就不要让这两个类发生直接的相互关系,如果需要调用里面的方法,可以通过第三者来转发调用。外观模式非常好的诠释了这段话。外观模式提供了一个统一的接口,用来访问子系统中的一群接口。它让一个应用程序中子系统间的相互依赖关系减少到了最少,它给子系统提供了一个简单、单一的屏障,客户通过这个屏障来与子系统进行通信。通过使用外观模式,使得客户对子系统的引用变得简单了,实现了客户与子系统之间的松耦合。但是它违背了“开闭原则”,因为增加新的子系统可能需要修改外观类或客户端的源代码。
外观模式包含如下角色:
Facade: 外观角色
SubSystem:子系统角色
11.亨元模式
在一个系统中对象会使得内存占用过多,特别是那些大量重复的对象,这就是对系统资源的极大浪费。享元模式对对象的重用提供了一种解决方案,它使用共享技术对相同或者相似对象实现重用。享元模式就是运行共享技术有效地支持大量细粒度对象的复用。系统使用少量对象,而且这些都比较相似,状态变化小,可以实现对象的多次复用。这里有一点要注意:享元模式要求能够共享的对象必须是细粒度对象。享元模式通过共享技术使得系统中的对象个数大大减少了,同时享元模式使用了内部状态和外部状态,同时外部状态相对独立,不会影响到内部状态,所以享元模式能够使得享元对象在不同的环境下被共享。同时正是分为了内部状态和外部状态,享元模式会使得系统变得更加复杂,同时也会导致读取外部状态所消耗的时间过长。
享元模式包含如下角色:
Flyweight: 抽象享元类
ConcreteFlyweight: 具体享元类
UnsharedConcreteFlyweight: 非共享具体享元类
FlyweightFactory: 享元工厂类
12.代理模式
代理模式就是给一个对象提供一个代理,并由代理对象控制对原对象的引用。它使得客户不能直接与真正的目标对象通信。代理对象是目标对象的代表,其他需要与这个目标对象打交道的操作都是和这个代理对象在交涉。代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了的作用和保护了目标对象的,同时也在一定程度上面减少了系统的耦合度。
代理模式包含如下角色:
Subject: 抽象主题角色
Proxy: 代理主题角色
RealSubject: 真实主题角色
13.访问者模式
访问者模式俗称23大设计模式中最难的一个。除了结构复杂外,理解也比较难。在我们软件开发中我们可能会对同一个对象有不同的处理,如果我们都做分别的处理,将会产生灾难性的错误。对于这种问题,访问者模式提供了比较好的解决方案。访问者模式即表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
访问者模式的目的是封装一些施加于某种数据结构元素之上的操作,一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变。为不同类型的元素提供多种访问操作方式,且可以在不修改原有系统的情况下增加新的操作方式。同时我们还需要明确一点那就是访问者模式是适用于那些数据结构比较稳定的,因为他是将数据的操作与数据结构进行分离了,如果某个系统的数据结构相对稳定,但是操作算法易于变化的话,就比较适用适用访问者模式,因为访问者模式使得算法操作的增加变得比较简单了。
访问者模式包含如下角色:
Vistor: 抽象访问者
ConcreteVisitor: 具体访问者
Element: 抽象元素
ConcreteElement: 具体元素
ObjectStructure: 对象结构
14.模板模式
有些时候我们做某几件事情的步骤都差不多,仅有那么一小点的不同,在软件开发的世界里同样如此,如果我们都将这些步骤都一一做的话,费时费力不讨好。所以我们可以将这些步骤分解、封装起来,然后利用继承的方式来继承即可,当然不同的可以自己重写实现嘛!这就是模板方法模式提供的解决方案。
所谓模板方法模式就是在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
模板方法模式就是基于继承的代码复用技术的。在模板方法模式中,我们可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中。也就是说我们需要声明一个抽象的父类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法让子类来实现剩余的逻辑,不同的子类可以以不同的方式来实现这些逻辑。所以模板方法的模板其实就是一个普通的方法,只不过这个方法是将算法实现的步骤封装起来的。
模板方法模式包含如下角色:
AbstractClass: 抽象类
ConcreteClass: 具体子类
15.策略模式 我们知道一件事可能会有很多种方式来实现它,但是其中总有一种最高效的方式,在软件开发的世界里面同样如此,我们也有很多中方法来实现一个功能,但是我们需要一种简单、高效的方式来实现它,使得系统能够非常灵活,这就是策略模式。 所以策略模式就是定义了算法族,分别封装起来,让他们之前可以互相转换,此模式然该算法的变化独立于使用算法的客户。 在策略模式中它将这些解决问题的方法定义成一个算法群,每一个方法都对应着一个具体的算法,这里的一个算法我就称之为一个策略。虽然策略模式定义了算法,但是它并不提供算法的选择,即什么算法对于什么问题最合适这是策略模式所不关心的,所以对于策略的选择还是要客户端来做。客户必须要清楚的知道每个算法之间的区别和在什么时候什么地方使用什么策略是最合适的,这样就增加客户端的负担。 同时策略模式也非常完美的符合了“开闭原则”,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。但是一个策略对应一个类将会是系统产生很多的策略类。
策略模式包含如下角色:
Context: 环境类
Strategy: 抽象策略类
ConcreteStrategy: 具体策略类
16.状态模式
在很多情况下我们对象的行为依赖于它的一个或者多个变化的属性,这些可变的属性我们称之为状态,也就是说行为依赖状态,即当该对象因为在外部的互动而导致他的状态发生变化,从而它的行为也会做出相应的变化。对于这种情况,我们是不能用行为来控制状态的变化,而应该站在状态的角度来思考行为,即是什么状态就要做出什么样的行为。这个就是状态模式。
所以状态模式就是允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。在状态模式中我们可以减少大块的if…else语句,它是允许态转换逻辑与状态对象合成一体,但是减少if…else语句的代价就是会换来大量的类,所以状态模式势必会增加系统中类或者对象的个数。同时状态模式是将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。但是这样就会导致系统的结构和实现都会比较复杂,如果使用不当就会导致程序的结构和代码混乱,不利于维护。
状态模式包含如下角色:
Context: 环境类
State: 抽象状态类
ConcreteState: 具体状态类
17.观察者模式
何谓观察者模式?观察者模式定义了对象之间的一对多依赖关系,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并且自动更新。在这里,发生改变的对象称之为观察目标,而被通知的对象称之为观察者。一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,所以么可以根据需要增加和删除观察者,使得系统更易于扩展。所以观察者提供了一种对象设计,让主题和观察者之间以松耦合的方式结合。
观察者模式包含如下角色:
Subject: 目标
ConcreteSubject: 具体目标
Observer: 观察者
ConcreteObserver: 具体观察者
18.备忘录模式
后悔药人人都想要,但是事实却是残酷的,根本就没有后悔药可买,但是也不仅如此,在软件的世界里就有后悔药!备忘录模式就是一种后悔药,它给我们的软件提供后悔药的机制,通过它可以使系统恢复到某一特定的历史状态。
所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它实现了对信息的封装,使得客户不需要关心状态保存的细节。保存就要消耗资源,所以备忘录模式的缺点就在于消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
备忘录模式包含如下角色:
Originator: 原发器
Memento: 备忘录
Caretaker: 负责人
19.中介者模式
租房各位都有过的经历吧!在这个过程中中介结构扮演着很重要的角色,它在这里起到一个中间者的作用,给我们和房主互相传递信息。在外面软件的世界里同样需要这样一个中间者。在我们的系统中有时候会存在着对象与对象之间存在着很强、复杂的关联关系,如果让他们之间有直接的联系的话,必定会导致整个系统变得非常复杂,而且可扩展性很差!在前面我们就知道如果两个类之间没有不必彼此通信,我们就不应该让他们有直接的关联关系,如果实在是需要通信的话,我们可以通过第三者来转发他们的请求。同样,这里我们利用中介者来解决这个问题。 所谓中介者模式就是用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。在中介者模式中,中介对象用来封装对象之间的关系,各个对象可以不需要知道具体的信息通过中介者对象就可以实现相互通信。它减少了对象之间的互相关系,提供了系统可复用性,简化了系统的结构。 在中介者模式中,各个对象不需要互相知道了解,他们只需要知道中介者对象即可,但是中介者对象就必须要知道所有的对象和他们之间的关联关系,正是因为这样就导致了中介者对象的结构过于复杂,承担了过多的职责,同时它也是整个系统的核心所在,它有问题将会导致整个系统的问题。所以如果在系统的设计过程中如果出现“多对多”的复杂关系群时,千万别急着使用中介者模式,而是要仔细思考是不是您设计的系统存在问题。
Mediator: 抽象中介者
ConcreteMediator: 具体中介者
Colleague: 抽象同事类
ConcreteColleague: 具体同事类
20.迭代器模式
对于迭代在编程过程中我们经常用到,能够游走于聚合内的每一个元素,同时还可以提供多种不同的遍历方式,这就是迭代器模式的设计动机。在我们实际的开发过程中,我们可能会需要根据不同的需求以不同的方式来遍历整个对象,但是我们又不希望在聚合对象的抽象接口中充斥着各种不同的遍历操作,于是我们就希望有某个东西能够以多种不同的方式来遍历一个聚合对象,这时迭代器模式出现了。
何为迭代器模式?所谓迭代器模式就是提供一种方法顺序访问一个聚合对象中的各个元素,而不是暴露其内部的表示。迭代器模式是将迭代元素的责任交给迭代器,而不是聚合对象,我们甚至在不需要知道该聚合对象的内部结构就可以实现该聚合对象的迭代。
通过迭代器模式,使得聚合对象的结构更加简单,它不需要关注它元素的遍历,只需要专注它应该专注的事情,这样就更加符合单一职责原则了。
迭代器模式包含如下角色:
Iterator: 抽象迭代器
ConcreteIterator: 具体迭代器
Aggregate: 抽象聚合类
ConcreteAggregate: 具体聚合类
21.解释器模式
所谓解释器模式就是定义语言的文法,并且建立一个解释器来解释该语言中的句子。解释器模式描述了如何构成一个简单的语言解释器,主要应用在使用面向对象语言开发的编译器中。它描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。
解释器模式包含如下角色:
AbstractExpression: 抽象表达式
TerminalExpression: 终结符表达式
NonterminalExpression: 非终结符表达式
Context: 环境类 Client: 客户类
22.命令模式
有些时候我们想某个对象发送一个请求,但是我们并不知道该请求的具体接收者是谁,具体的处理过程是如何的,们只知道在程序运行中指定具体的请求接收者即可,对于这样将请求封装成对象的我们称之为命令模式。所以命令模式将请求封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。同时命令模式支持可撤销的操作。
命令模式可以将请求的发送者和接收者之间实现完全的解耦,发送者和接收者之间没有直接的联系,发送者只需要知道如何发送请求命令即可,其余的可以一概不管,甚至命令是否成功都无需关心。同时我们可以非常方便的增加新的命令,但是可能就是因为方便和对请求的封装就会导致系统中会存在过多的具体命令类。
命令模式包含如下角色:
Command: 抽象命令类
ConcreteCommand: 具体命令类
Invoker: 调用者
Receiver: 接收者
Client:客户类
23.责任链模式
职责链模式描述的请求如何沿着对象所组成的链来传递的。它将对象组成一条链,发送者将请求发给链的第一个接收者,并且沿着这条链传递,直到有一个对象来处理它或者直到最后也没有对象处理而留在链末尾端。
避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止,这就是职责链模式。在职责链模式中,使得每一个对象都有可能来处理请求,从而实现了请求的发送者和接收者之间的解耦。同时职责链模式简化了对象的结构,它使得每个对象都只需要引用它的后继者即可,而不必了解整条链,这样既提高了系统的灵活性也使得增加新的请求处理类也比较方便。但是在职责链中我们不能保证所有的请求都能够被处理,而且不利于观察运行时特征。
职责链模式包含如下角色:
Handler: 抽象处理者
ConcreteHandler: 具体处理者
Client: 客户类