一、设计模式的六大原则
总原则---开闭原则:
对于拓展是开放的,对于修改是封闭的。即对于一个功能的拓展不应该是通过修改原来的代码实现,而是应该通过添加新代码实现。但有时这个情况也是不可避免的。
1. 单一职责原则
对于一个类而言,应该仅有一个引起它变化的原因。
2. 里氏替换原则
子类型必须能够替换掉它们的父类型。只有子类能替换掉父类,软件单位的功能不受影响时,父类才能真正被复用,而子类也能够在父类的基础上增加新的行为。
3. 依赖倒转原则
开闭原则的基础。原话:抽象不应该依赖细节,细节应该依赖于抽象。即针对接口编程。同时高层模块不应该依赖底层模块,两者都应该依赖于抽象。
4. 接口隔离原则
每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。
5. 迪米特法则(最少知道原则)
如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
6. 合成/聚合复用原则
尽量优先使用合成/聚合的方式,而不是继承。这样子有助于保持每个类被封装,并被集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长为不受控制的庞然大物。
二、创建型模式
1. 简单工厂模式(该模式不属于23种模式中的)
通过简单的一个工厂类来创建相应的对象,将生成产品类的代码跟客户端代码分离,但与此同时,其违反了开闭原则(因为每增加一个产品类都需修改工厂了)。
2. 工厂方法模式(Factory Method)
定义: 定义一个用于创建对象的接口,让子类决定实例化哪个类。工厂方法使一个类的实例化延迟到其子类。
只有一个抽象产品类和一个抽象工厂类。每个具体工厂类只能实例化一个具体产品类。克服了简单工厂模式违背开闭原则的弊端,通过创建一个工厂接口,每次新增产品,则新建一个工厂类实现该接口即可。但当产品类过多时,也会带来工厂类过多的弊端。
3. 抽象工厂模式(Abstract Factory)
定义: 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
具有多个抽象产品类和一个抽象工厂类。每个具体工厂类可以实例化多个具体产品类。符合开闭原则。
与工厂方法模式的区别:工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。工厂方法用于创建 "一种" 产品,着重点在于"怎么创建"。抽象工厂则是创建一系列产品,着重点在于"创建哪些"产品上。
4. 建造者模式(Builder)
定义: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
Android中存在一种通过静态内部类(Builder)等方式实现的对象属性的无序化构造的建造者模式,Android中的对话框多用此种形式创建。
5. 单例模式
定义:保证一个类只有一个实例,并提供一个访问它的全局访问点。
单例模式的八种写法比较
6. 原型模式(Prototype)
定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
由于该模式十分常用,故java中已经提供了该模式的相关实现。java中复制分为浅复制和深复制。
- 浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的。
- 深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。
/* 相应的对象要实现Cloneable和Serializable接口
Cloneable接口是空方法,clone和deepClone等方法的名字可以随便起
*/
/* 浅复制 */
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
/* 深复制(通过序列化,也可通过直接创建新对象实现) */
public Object deepClone() throws IOException, ClassNotFoundException {
/* 写入当前对象的二进制流 */
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
/* 读出二进制流产生的新对象 */
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
三、结构型模式
7种结构型模式:适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模式。其中对象的适配器模式是各种模式的起源
1. 适配器模式(Adapter)
定义: 将一个类的接口转换成客户希望的另外一个接口。该模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
该模式有两种,类适配器和对象适配器。在GoF的设计模式中,类适配器是通过多重继承来实现的,但java不支持多重继承,所以在这里结构图给出的是对象适配器的。该模式一般在软件开发后期或者维护期再考虑使用。
2. 桥接模式(Bridge)
定义: 将抽象部分与它的实现部分分离,使它们都可以独立地变化。(抽象与它的实现分离,并不是指让抽象类和其派生类分离,实现指的是抽象类和它的派生类用来实现自己的对象)
通俗理解:实现系统可能有多角度的分类,每一种分类都有可能变化,那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合。利于拓展。
举例:手机可以按照手机品牌分类,每种品牌下有其各自品牌的手机软件。
手机也可以按照手机软件分类,每种软件下有各个品牌的手机软件。
所以我们可以将品牌和软件进行分离,得到如下的结构:
这种结构就是桥接模式,其核心就是把这些实现独立出来,让它们各自地变化,使其不会相互影响,达到应对变化的目的。否则,每当我们需要新增品牌或者软件时,都要新建许多类。
3. 组合模式(Composite)
定义:将对象组合成树形结构以表示 '部分-整体' 的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性
组合模式分为安全组合模式和透明(一致性)组合模式。
透明方式指的是Component中声明所有用来管理子对象的方法,其中包括Add、Remove等,优点是叶子节点和枝节点对于外界没有区别,具备完全一致的行为接口,缺点是叶子节点实现Add、Remove等方法没有意义。
安全方式指的是Component中不声明Add、Remove等方法,而是在Composite中声明所有用来管理子类对象的方法,缺点是树叶和树枝类不具有相同的接口,客户端的调用需要做相应的判断。
使用场景:需求中体现了部分与整体层次的结构,或者可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所以对象时。
4. 装饰模式(Decorator)
定义: 动态地给对象增加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
优点:将类中的装饰功能从类中搬移去除,起到简化原有的类的作用。更大的好处是有效的把类的核心职责和装饰功能区分开,并可以去除相关类中重复的装饰逻辑。
5. 外观模式(Facade)
定义: 为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
外观模式将类与类之间的关系集中到Facade类中一起管理,降低了类与类之间的耦合度。该模式完美的体现了依赖倒转原则和迪米特法则。
使用场景:
设计初期阶段,应有意识的分离不同的层,在层与层之间建立外观,降低耦合。
开发阶段,子系统因为不断的重构演化越来越复杂,大部分模式使用时也会产生很多很小的类,增加外观可以提供一个简单的接口,减少它们之间的依赖。
系统维护阶段,旧系统难以维护和拓展,可以为旧系统开发一个外观Facade类,让新系统与Facade对象交互,Facade与遗留代码交互所有复杂的工作。
6. 享元模式(Flyweight)
定义: 运用共享技术有效地支持大量细粒度的对象
FlyWeightFactory负责创建和管理享元单元,当一个客户端请求时,工厂需要检查当前对象池中是否有符合条件的对象,如果有,就返回已经存在的对象,如果没有,则创建一个新对象,FlyWeight是超类或者接口。
内部状态:在享元对象内部并且不会随环境改变而改变的共享部分,称为享元对象的内部状态。
外部状态:在享元对象内部并且会随环境改变而改变的不能共享的部分,称为享元对象的外部状态。
应用场景:
- 如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时可以考虑使用;
- 还有就是对象的大多数状态为外部状态,如果删除对象的外部状态,可以用相对较少的共享对象取代很多对象,可以考虑使用
7. 代理模式(Proxy)
定义: 为其它对象提供一种代理以控制对这个对象的访问。
应用场景:
远程代理:为一个对象在不同的地址空间提供局部代表,隐藏一个对象存在于不同地址空间的事实。
虚拟代理:根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象。
安全代理:用来控制真实对象访问时的权限
智能指引: 当调用真实对象时,代理处理另外一些事情。
四、行为型模式
第一类:通过父类与子类的关系进行实现。
第二类:两个类之间。
第三类:类的状态。
第四类:通过中间类
1. 策略模式(Strategy)
定义: 定义了算法家族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化,不会影响到使用算法的客户。
优点:减少了各种算法类与使用算法类之间的耦合;Strategy类层次为Context定义了一系列可供重用的算法或行为,继承有助于析取出这些算法中的公共功能;简化了单元测试,每个算法都有自己类,可以通过自己的接口单独测试。
基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并转给Context对象。也可与简单工厂模式结合,由Context承担该职责。
策略模式与工厂的区别
2. 职责链模式(Chain of Responsibility)
定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
特别的,在一个时刻,命令只允许由一个对象传给另一个对象,而不允许传给多个对象
优点:接收者和发送者都没有双方的明确信息,且链中的对象自己也不知道链的结构。结果是职责链可简化对象的相互连接,它们仅需保持一个指向其后继者的引用,而不需保持它所有的候选接收这点引用;可以随时增加或修改处理一个请求的结构,增强了给对象指派职责的灵活性。
3. 命令模式(Command)
定义: 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或则记录请求日志,以及支持可撤销的操作。
优点:较容易设计一个命令队列;需要时,较容易将命令记入日志;允许接收请求一方决定是否要否决请求;容易实现对请求的撤销和重做;容易增加具体命令类;把请求一个操作的对象与知道怎么执行一个操作的对象分割开。
4. 解释器模式(Interpreter)
定义: 给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
优点:容易改变和拓展文法,因为该模式使用类来表示文法规则,可使用继承来改变或者拓展文法,也比较容易实现文法,因为定义抽象语法树中各个节点的类的实现大体类似,都易于直接编写
缺点:该模式为文法中的每一条规则至少定义了一个类,因为当文法包含许多规则时可能难以管理和维护。文法复杂时,一般使用语法分析程序或者编译器生成器来处理。
5. 迭代器模式(Iterator)
定义: 提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。
迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器来负责,这样既可以做到不暴露集合的内部结构,又让外部代码透明地访问集合内部的数据。
6. 中介者模式(Mediator)
定义: 用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立的改变它们之间的交互。
优点:Mediator的出现减少了各个Colleague的耦合,使得可以独立的改变和复用各个Colleague类和Mediator;由于把对象如何协作进行了抽象,将中介作为一个独立的概念并将其封装在一个对象中,把关注的重点从对象各自本身的行为转移到它们之间的交互上来,能站在更宏观的角度去看待系统。
缺点:由于ConcreteMediator控制了集中化,交互的复杂性变为了中介者的复杂性,会使得中介者变得比任何一个ConcreteColleague都复杂。
应用场景:一搬应用于一组对象以定义良好但是复杂的方式进行通信的场合。以及想定制一个分布在多个类中的行为,而又不想生成太多的子类的场合。
7. 备忘录模式(Mememto)
定义: 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
Memento有两个接口,CareTaker只能看到Memento的窄接口,它只能将备忘录传递给其它对象。Originator能看到一个宽接口,允许它访问返回到先前状态所需的所有数据。
应用场景:比较适用于功能比较复杂的,但需要维护和记录属性历史的类。
8. 观察者模式(Observer)
定义: 定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生改变时,会通知所有观察者对象,使它们能够自动更新自己。
使用场景:当一个对象的改变需要同时改变其它对象时,且它不知道具体有多少对象有待改变;
9. 访问者模式(Visitor)
定义: 表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些新元素的操作。
访问者模式适用于数据结构相对稳定的系统。目的在于把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由地演化(增加或删除等)。
如果系统具有比较稳定的数据结构,又有易于变化的算法,则比较适合使用该模式。反之数据结构对象易于变化,则不适合。
事实上,很难找到数据结构不变化的情况,所以该模式使用较少。大多数情况并不需要使用该模式,但当一旦你需要该模式,那就是真的需要了。
优点:利于增加新的操作,因为增加新的操作意味着增加一个新的访问者。访问者模式将有关的行为集中到一个访问者对象中。
缺点:难以增加新的数据结构。
10. 状态模式(State)
定义: 当一个对象的内部状态改变时允许改变其行为,这个对象看起来像是改变了其类。
该模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时,把状态的判断转移逻辑迁移到表示不同状态的一系列类中,简化复杂的判断逻辑。
优点:将与特定状态相关的行为局部化,并且将不同状态的行为分割开来
使用场景:当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时。
11. 模板方法模式
定义: 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
优点:通过将不变行为搬移到超类,去除了子类中的重复代码。
参考资料:
《大话数据结构》
23种设计模式全解析
结合实例分析简单工厂模式&工厂方法模式&抽象工厂模式的区别