《研磨设计模式》--摘录笔记(一)
说明:本文档大部分内容是从原书中摘录出来的,使用步骤是自己总结提炼的。
目的:内容仅供自己温习回顾使用。
提示:可能会存在摘录不全或者总结不精确的问题。若是他人阅读,仅供参考!---------dusuanyun 2016-0911
第一章:设计模式基础
设计模式:是指在软件开发中,经过验证的,用于解决在特定环境下,重复出现的、特定问题的解决方案。
这23种设计模式,GoF把它们分为三类。
创建型模式:抽象了对象实例化过程,用来帮助创建对象的实例。
行为型模式:描述算法和对象间职责的分配。
结构型模式:描述如何组合类和对象以获得更大的结构。
要从思想上和方法上吸收设计模式的精键,并融入到自己的思路中,在进行软件的分析和设计的时候,能随意地、自然而然地应用,就如同自己思维的一部分.
第2 章 简单工厂(GoF的著作中没有)
定义:提供一个创建对象实例的功能,而无须关心其具体实现。被创建实例的类型可以是接口、 抽象类,也可以是具体的类。
本质:选择实现
使用步骤:
1.创建一个xxxFactory的工厂类;2.定义客户所需要的功能接口-API;3.定义API的具体实现类(可以有多个)
4.xxxFactory类中提供静态(static)方法:createAPI(),在方法中new具体的实现类赋予API,返回类型为API。
createAPI(),可以有参数,在方法内部选择不同的实现类。
优点:帮助封装;解耦
缺点:可能增加客户端的复杂度;不方便扩张子工厂。
易混模式:
A.抽象工厂模式:如果抽象工厂退化成只有一个实现,不分层次,那么就相当于简单工厂了。
B.工厂方法模式:如果把工厂方法中选择的实现放到父类中直接实现,那就等同于简单工厂。
何时选用:
• 如果想要完全封装隔离具体实现,让外部只能通过接口来操作封装体,那么可以选用简单工厂,让客户端通过工厂来获取相应的接口,而无须关心具体的实现。
• 如果想要把对外创建对象的职责集中管理和控制,可以选用简单工厂,一个简单工厂可以创建很多的、不相关的对象,可以把对外创建对象的职责集中到一个简单工厂来,从而实现集中管理和控制。
第3章 外观模式(GoF的著作中划分为结构型)
定义:为子系统中的一组接口提供一个一致的界面, Facade模式定义了一个高层接口, 这个接口使得这一子系统更加容易使用。
本质:封装交互,简化调用
使用步骤:
1.定义多个模块的功能接口--API,eg:xxxModuleAPI; 2.定义API的具体实现类,eg:xxxModuleImpl;
3.定义一个xxxFacade的外观类,外观类定义供客户端调用的方法,在方法中需要什么具体的实现类就new什么。
优点:松散耦合;简单易用;更好地划分访问层次。
缺点:过多或不太合理的Facade也容易让人迷惑,到底是调用Facade好呢,还是直接调用模块好。
易混模式:中介者模式
外观模式是封装内部,方便外部使用;中介者模式是都是同事之间的关系,不分内外------自己总结的
何时选用:
1.如果你希望为一个复杂的子系统提供一个单接口的时候,可以考虑使用外观模式。
2.如果想要让客户程序,和抽象类的实现部分松散耦合,可以考虑使用外观模式,
3.如果构建多层结构的系统,可以考虑、使用外观棋式,使用外观对象作为每层的入口, 这样可以简化层问调用,也可以松散层次之间的依赖关系。
第4 章 适配器模式(GoF的著作中划分为结构型)
定义:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
本质:转换匹配,复用功能。
使用步骤:【涉及名词】Target:定义客户端需要的跟特定领域相关的接口。
Adaptee:己经存在的接口,通常能满足客户端的功能要求,但是接口与客户端要求的特定领域接口不一致,需要被适配。
Adapter:适配器,把Adaptee适配成为Client需要的Target。
1.定义xxxTarget接口;2.定义xxxAdapter实现xxxTarget;
3.在xxxAdapter中,定义保存xxxAdaptee的私有属性;
4.在xxxAdapter的构造方法中以参数的形式,传入xxxAdaptee,在构造方法中对xxxAdaptee的私有属性赋值。
5.在xxxAdapter的构造方法中,提供需要的转换方法。
6.客户端使用,先创建要适配的xxxAdaptee,创建xxxTarget并进行使用xxxAdaptee为参数进行初始化,最后调用xxxTarget提供转换的方法。
优点:更好的复用性;更好的可扩展性。
缺点:过多地使用适配器,会让系统非常凌乱,不容易整体进行把握。
易混模式:---
何时选用:
• 如果你想要使用一个已经存在的类,但是它的接口不符合你的需求,这种情况可以使用适配器模式,来把已有的实现转换成你需要的接口。
• 如果你想创建一个可以复用的类,这个类可能和一些不兼容的类一起工作,这种情况可以使用适配器模式,到时候需要什么就适配什么。
• 如果你想使用一些已经存在的子类,但是不可能对每一个子类都进行适配,这种情况可以选用对象适配器,直接适配这些子类的父类就可以了。
第5 章 单例模式(GoF的著作中划分为创建型)
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
本质:控制实例数目。
使用步骤:分为饱汉式和饿汉式
懒汉式步骤
1.私有化构造方法;2.提供获取实例的静态(static)方法getInstance();3.定义存储实例的静态(static)私有(private)属性;4.控制实例的创建,如果存储实例的属性不为null,则返回属性值,否则创建实例并对属性赋值。
饿汉式步骤:和上面的区别
1.在定义存储实例的静态(static)私有(private)属性时,直接初始化---new了;
2.getInstance()不在判断属性为null了,直接返回属性值。
优点:时间空间;线程安全
缺点:
易混模式:---
何时选用:
当需要控制一个类的实例只能有一个,而且客户只能从一个全局访问点访问它时,可以选用单例模式,这些功能恰好是单例模式要解决的问题。
第6 章 工厂方法模式(GoF的著作中划分为创建型)
定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类,FactorMethod使一个类的实例化延迟到其子类。
本质:延迟到子类来选择实现。
使用步骤:【涉及名词】
Product:定义工厂方法所创建的对象的接口, 也就是实际需要使用的对象的接口。
ConcreteProduct: 具体的Product 接口的实现对象。
Creator: 创建器,声明工厂方法,工厂方法通常会返回一个Product 类型的实例对象,而且多是抽象方法。也可以在Creator 里面提供工厂方法的默认实现,让工厂方法返回一个缺省的Product 类型的实例对象。
ConcreteCreator :具体的创建器对象, 覆盖实现Creator 定义的工厂方法, 返回具体的Product 实例。
1.定义Product接口对象;2.定义实现Product接口对象ConcreteProduct;3.定义创建器的抽象(abstract)类Creator
4.定义具体的创建器对象ConcreteCreator,并继承Creator。
5.在ConcreteCreator中定义protected Product factoryMethod(),在方法中new ConcreteProduct()并返回。
优点:可以在不知具体实现的情况下开始编程;更容易扩展对象的新版本;连接平行的类层次。
缺点:具体产品对象和工厂方法的耦合性。
何时选用:
1.如果一个类需要创建某个接口对象,但是又不知道具体的实现,这种情况可以选用工厂方法模式,把创建对象的工作延迟到子类中去实现。
2.如果一个类本身就希望由它的子类来创建所需的对象的时候,应该使用工厂方法模式。
第7 章 抽象工厂模式(GoF的著作中划分为创建型)
定义:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
本质:选择产品簇的实现。
使用步骤:【涉及名词】
Abstract Factory:抽象工厂,定义创建一系列产品对象的操作接口。
Concrete Factory:具体的工厂,实现抽象工厂定义的方法,具体实现一系列产品对象的创建。
Abstract Product:定义一类产品对象的接口。
Concrete Product:具体的产品实现对象,通常在具体工厂里面,会选择具体的产品实现对象,来创建符合抽象工厂定义的方法返回的产品类型的对象。
Client:客户端,主要使用抽象工厂来获取一系列所需要的产品对象,然后面向这些产品对象的接口编程,以实现需要的功能。
1.定义抽象工厂AbstractFactory接口;2.定义抽象产品Abstract Product的接口,eg:AbstractProductA、Abstract ProductB接口;
3.定义具体产品实现对象ConcreteProduct,实现Abstract Product的接口,eg:ConcreteProductA、ConcreteProductB;
4.具体工厂ConcreteFactory实现抽象工厂AbstractFactory,eg:ConcreteFactory1、ConcreteFactory2、...
5.在具体的ConcreteFactory1中实现创建具体相关的产品对象。Eg:createProductA()、createProductB()
在具体的ConcreteFactory2中实现创建具体相关的产品对象。Eg:createProductA2()、createProductB2()
6.客户端在使用抽象工厂功能时,创建(new)指定的ConcreteFactory,eg:ConcreteFactory1
其实ConcreteFactoryN就好像schemaN,对应方案一、方案二...
优点:分离接口和实现;使得切换产品簇变得容易。
缺点:不太容易扩展新的产品;容易造成类层次复杂。
何时选用:
1. 如果希望一个系统独立于它的产品的创建、组合和表示的时候。换句话说希望一个系统,只是知道产品的接口,而不关心实现的时候。
2.如果一个系统要由多个产品系列中的一个来配置的时候。换句话说可以动态切换产品簇的时候。
3.如果要强调一系列相关产品的接口,以便联合使用它们的时候。
第8 章 生成器模式(GoF的著作中划分为创建型)
定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
本质:分离整体构建算法和部件构造。
使用步骤:【涉及名词】
• Builder:生成器接口,定义创建一个Product对象所需的各个部件的操作。
• ConcreteBuilder: 具体的生成器实现,实现各个部件的创建, 并负责组装Product对象的各个部件, 同时还提供一个让用户获取组装完成后的产品对象的方法。
• Director:指导者,也被称为导向者, 主要用来使用Builder接口,以一个统一的过程来构建所需要的Product对象。
• Product:产品,表示被生成器构建的复杂对象, 包含多个部件。
1.定义生成器接口Builder;2.定义具体生成器实现类ConcreteBuilder实现Builder;
3.定义被构建的产品对象接口Product;4.定义指导者类Director,在Director定义私有属性builder来存储,使用构造函数传进来的Builder。构造函数含有参数Builder。
5.在指导者Director中提供构造方法,在该方法中调用具体的builder的构造功能!
优点:松散耦合;可以很容易的改变产品内部的表示;更好的复用性。
缺点:---
何时选用:
1.如果创建对象的算法,应该独立于该对象的组成部分以及它们的装配方式时。
2.如果同一个构建过程有着不同的表示时。
第9 章 原型模式(GoF的著作中划分为创建型)
定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
本质:克隆生成对象。
使用步骤:【涉及名词】
Prototype: 声明一个克隆自身的接口,用来约束想要克隆自己的类,要求它们都要实现这里定义的克隆方法;
ConcretePrototype: 实现Prototype接口的类, 这些类真正实现克隆自身的功能;
Client:使用原型的客户端, 首先要获取到原型实例对象,然后通过原型实例克隆自身来创建新的对象实例。
1.定义原型接口---Prototype;2.定义实现原型接口实现类ConcretePrototype来实现Prototype,eg:ConcretePrototypeA、ConcretePrototypeB....
3.客户端Client直接调用对象实例的clone()方法创建新的实例。
优点:对客户端隐藏具体的实现类型;在运行时动态改变具体的实现类型。
缺点:最大的缺点就在于每个原型的子类都必须实现clone的操作,尤其在包含引用类型的对象时, clone方法会比较麻烦, 必须要能够递归地让所有的相关对象都要正确地实现克降。
何时选用:
• 如果一个系统想要独立于它想要使用的对象时,可以使用原型模式,让系统只面向接口编程,在系统需要新的对象的时候,可以通过克隆原型来得到。
• 如果需要实例化的类是在运行时刻动态指定时,可以使用原型模式,通过克隆原型来得到需要的实例。
第10 章 中介者模式(GoF的著作中划分为行为型)
定义:用一个中介对象来封装一系列的对象交互。中介者使得各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
本质:封装交互。
使用步骤:【涉及名词】
• Mediator:中介者接口。在里面定义各个同事之间交互需要的方法,可以是公共的通信方法,比如changed 方法,大家都用,也可以是小范围的交互方法。
• ConcreteMediator:具体中介者实现对象。它需要了解并维护各个同事对象,并负责具体的协调各同事对象的交互关系。
• Colleague:同事类的定义,通常实现成为抽象类,主要负责约束同事对象的类型,并实现一些具体同事类之间的公共功能.比如,每个具体同事类都应该知道中介者对象,也就是具体同事类都会持有中介者对象,都可以定义到这个类里面。
• ConcreteColleague:具体的同事类,实现自己的业务,在需要与其他同事通信的时候,就与持有的中介者通信,中介者会负责与其他的同事交互。
1.定义所有同事的(抽象)父类Colleague;2.Colleague中持有Mediator,同时构造函数以Mediator为参数;
3.定义具体的ConcreteColleague实现Colleague,eg:ConcreteColleagueA、ConcreteColleagueB ....
在具体的ConcreteColleague中的构造函数调用父类的实现(也是以Mediator为参数),同时提供与其他同事交互的方法;
4.定义中介者接口Mediator,在此接口中提供以Colleague为参数的changed()方法;
5.定义中介者接口的实现ConcreteMediator,在该对象中持有所有需要交互的同事对象,eg:ConcreteColleagueA、ConcreteColleagueB ....
同时实现接口中changed()方法。
优点:松散耦合;集中控制交互;多对多变成一对多。
缺点: 过度集中化。如果同事对象的交互非常多,而且比较复杂,当这些复杂性全部集中到中介者的时候,会导致中介者对象变得十分的复杂,而且难以管理和维护。
何时选用:
1.如果一组对象之间的通信-方式比较复杂,导致相互依赖、结构混乱,可以采用中介者模式,把这些对象相互的交互管理起米, 每个对象都只需要和中介者交互,从而使得各个对象松散耦合,结构也更清晰易懂。
2.如果一个对象引用很多的对象,并直接跟这些对象交互,导致难以复用该对象,可以采用中介者模式,把这个对象跟其他对象的交互封装到中介者对象里面,这个对象只需要和中介者对象交互就可以了。
第11章 代理模式(GoF的著作中划分为结构型)
定义:为其他对象提供一种代理以控制对这个对象的访问。
本质:控制(对)对象访问
使用步骤:【涉及名词】
Proxy: 代理对象, 通常具有如下功能。实现与具体的目标对象一样的接口, 这样就可以使用代理来代替具体的目标对象。保存一个指向具体目标对象的引用, 可以在需要的时候调用具体的目标对象。
可以控制对具体目标对象的访问, 并可以负责创建和删除它。
Subject:目标接口, 定义代理和具体目标对象的接口, 这样就可以在任何使用具体目标对象的地方使用代理对象。
RealSubject,具体的目标对象, 真正实现目标接口要求的功能。
1.定义目标接口Subject,并提供相关的接口方法;2.定义真实对象RealSubject实现Subject;3.定义代理对象Proxy来实现Subject。4.在Proxy中持有RealSubject,且构造函数以RealSubject为参数。
优点:---
缺点:---
何时选用:
1.需要为一个对象在不同的地址空间提供局部代表的时候,可以使用远程代理.
2.需要按照需要创建开销很大的对象的时候,可以使用虚代理。
3.需要控制对原始对象的访问的时候,可以使用保护代理。
4.需要在访问对象执行一些附加操作的时候,可以使用智能指引代理。
第12 章 观察者模式(GoF的著作中划分为行为型)
定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
本质:触发联动。
使用步骤:【涉及名词】
• Subject: 目标对象,通常具有如下功能。
• 一个目标可以被多个观察者观察。
• 目标提供对观察者注册和退订的维护。
• 当目标的状态发生变化时, 目标负责通知所有注册的、有效的观察者。
• ConcreteSubject: 具体的目标实现对象,用来维护目标状态, 当目标对象的状态发生改变时, 通知所有注册的、有效的观察者,让观察者执行相应的处理。
• Observer:定义观察者的接口,提供目标通知时对应的更新方法,这个更新方法进行相应的业务处理,可以在这个方法里面回调目标对象,以获取目标对象的数据。
• ConeretObserver: 观察者的具体实现对象,用来接收目标的通知, 并进行相应的后续处理,比如更新自身的状态以保持和目标的相应状态一致。
1.定义目标对象类Subject,在类中定义持有Observer的List,同时提供注册、删除、通知观察者对象的方法;
2.具体目标对象ConcreteSubject继承Subject,定义存储当前状态的属性subjectStat,在setSubjectStat方法中,设置属性后,调用父类的通知观察者的方法;
3.定义观察者接口Observer,其中提供以Subject为参数的update()方法;
4.Observer的实现类ConeretObserver,持有存储观察者状态的属性observerStat,同时实现update方法,在update方法中,可以进行状态对比,然后决定执行什么操作。
Subject对象中的通知观察者方法,核心是,遍历List中的Observer,并调用Observer的update方法。
优点:观察者模式实现了观察者和目标之间的抽象耦合;观察者模式实现了动态联动;观察者模式支持广播通信。
缺点:由于观察者模式每次都是广播通信,不管观察者需不需要,每个观察者都会被调用update方法,如果观察者不需要执行相应处理,那么这次操作就浪费了。其实浪费了还好,最怕引起误更新,那就麻烦了...
何时选用:
• 当一个抽象模型有两个方面, 其中一个方面的操作依赖于另一个方面的状态变化,那么就可以选用观察者模式, 将这两者封装成观察者和目标对象, 当目标对象变化的时候, 依赖于它的观察者对象也会发生相应的变化。这样就把抽象模型的这两个方面分离开了, 使得它们可以独立地改变和复用。
• 如果在更改一个对象的时候, 需要同时连带改变其他的对象, 而且不知道究竟应该有多少对象需要被连带改变, 这种情况可以选用观察者模式, 被更改的那一个对象很明显就相当于是目标对象, 而需要连带修改的多个其他对象,就作为多个观察者对象了。
• 当一个对象必须通知其他的对象, 但是你又希望这个对象和其他被它通知的对象是松散耦合的。也就是说这个对象其实不想知道具体被通知的对象。这种情况可以选用观察者模式,这个对象就相当于是目标对象, 而被它通知的对象就是观察看对象了。
第13章 命令模式(GoF的著作中划分为行为型)
定义:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
本质:封装请求。
使用步骤:【涉及名词】
Command:定义命令的接口,声明执行的方法。
ConcreteCommand:命令接口实现对象,是“ 虚” 的实现:通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
Receiver:接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
Invoker:要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
Client :创建具体的命令对象,并且设置命令对象的接收者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,把这个Client称为装配者会更好理解,因为真正使用命令的客户端是从Invoker来触发执行。
1.定义命令接口--Command,接口中含有execute()方法;2.定义具体的命令ConcreteCommand实现Command接口,持有接收者对象Receiver,以及命令自身状态state,构造函数以Receiver为参数,实现execute()方法(其中调用receiver.action())。
3.定义Receiver对象类,提供action方法,真正执行命令的相应操作;
4.定义调用者Invoker对象类,持有Command对象,提供setCommand方法--设置调用者持有的命令对象,提供runCommand()方法,方法中调用command.execute() ;
5.客户端实现:A.先创建接收者Receiver对象,然后以Receiver为参数创建Command;B.创建Invoker对象,setCommand。
注意:这个不是我们通常意义上的测试客户端.主要功能是要创建命令对象并设定它的接收者,因此这里并没有调用执行的代码。
优点:更松散的耦合;更动态的控制;很自然的复合命令;更好的扩展性。
缺点:---
何时选用:
• 如果需要抽象出需要执行的动作, 并参数化这些对象, 可以选用命令模式。将这些需要执行的动作抽象成为命令, 然后实现命令的参数化配置。
• 如果需要在不同的时刻指定、排列和执行请求, 可以选用命令模式。将这些请求封装成为命令对象, 然后实现将请求队列化。
• 如果需要支持取消操作, 可以选用命令模式, 通过管理命令对象, 能很容易地实现命令的恢复和重做功能。
• 如果需要支持当系统崩溃时,能将系统的操作功能重新执行一遍, 可以选用命令模式。将这些操作功能的请求封装成命令对象, 然后实现日志命令, 就可以在系统恢复以后, 通过日志获取命令列衰,从而重新执行一遍功能。
• 在需要事务的系统中, 可以选用命令模式。命令模式提供了对事务进行建模的方法。命令模式有一个别名就是Transaction。