本文梳理自:https://lrh1993.gitbooks.io/android_interview_guide/content/
一、创建型模式
- 创建型模式(Creational Pattern)对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。
-
为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不清楚其具体的实现细节,使整个系统的设计更加符合单一职责原则。
-
创建型模式隐藏了类的实例的创建细节,通过隐藏对象如何被创建和组合在一起达到使整个系统独立的目的。
-
1、简单工厂模式
(1)原理:
- 通过对应参数调用工厂的静态方法来指定工厂生产的产品是什么
(2)组成:
-
Factory:工厂角色
- 工厂角色负责实现创建所有实例的内部逻辑
-
Product:抽象产品角色
- 抽象产品角色是所创建的所有对象的父类,负责描述所有实例所共有的公共接口
-
ConcreteProduct:具体产品角色
- 具体产品角色是创建目标,所有创建的对象都充当这个角色的某个具体类的实例。
(3)优缺点:
-
优点:
-
通过使用工厂类,外界不再需要关心如何创造各种具体的产品,只要提供一个产品的名称作为参数传给工厂,就可以直接得到一个想要的产品对象,并且可以按照接口规范来调用产品对象的所有功能(方法)。
-
构造容易,逻辑简单。
-
-
缺点:
-
简单工厂模式中的if else判断非常多,完全是Hard Code,如果有一个新产品要加进来,就要同时添加一个新产品类,并且必须修改工厂类,再加入一个 else if 分支才可以, 这样就违背了 “开放-关闭原则”中的对修改关闭的准则了。当系统中的具体产品类不断增多时候,就要不断的修改工厂类,对系统的维护和扩展不利。
-
一个工厂类中集合了所有的类的实例创建逻辑,违反了高内聚的责任分配原则,将全部的创建逻辑都集中到了一个工厂类当中,所有的业务逻辑都在这个工厂类中实现。什么时候它不能工作了,整个系统都会受到影响。因此一般只在很简单的情况下应用,比如当工厂类负责创建的对象比较少时。
-
简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。
-
(4)适用环境
-
工厂类负责创建的对象比较少:由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。
-
客户端只知道传入工厂类的参数,对于如何创建对象不关心:客户端既不需要关心创建细节,甚至连类名都不需要记住,只需要知道类型所对应的参数。
2、工厂模式
(1)原理
- 工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象
-
这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。
-
每个工厂只负责生产一种产品(一一对应)
- 使用时通过将具体工厂赋给工厂父类的多态实现目标的指定
Factory factory = new ConcreteFractory(); Product product = factory.CreateProduct();
- 使用时通过将具体工厂赋给工厂父类的多态实现目标的指定
-
(2)组成
-
Product:抽象产品
- 工厂方法模式所创建的对象的超类,也就是所有产品类的共同父类或共同拥有的接口。
- 在实际的系统中,这个角色也常常使用抽象类实现。
-
ConcreteProduct:具体产品
- 这个角色实现了抽象产品(Product)所声明的接口,工厂方法模式所创建的每一个对象都是某个具体产品的实例。
-
Factory:抽象工厂
- 担任这个角色的是工厂方法模式的核心,任何在模式中创建对象的工厂类必须实现这个接口。
- 在实际的系统中,这个角色也常常使用抽象类实现。
-
ConcreteFactory:具体工厂
- 担任这个角色的是实现了抽象工厂接口的具体Java类。
- 具体工厂角色含有与业务密切相关的逻辑,并且受到使用者的调用以创建具体产品对象。
(3)优缺点
-
优点
-
工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
-
基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类。
-
使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”,这点比简单工厂模式更优秀。
-
-
缺点
-
在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
-
由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
-
(4)适用场景
-
一个类不知道它所需要的对象的类:
- 在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。
-
一个类通过其子类来指定创建哪个对象:
- 在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
-
将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。
3、抽象工厂模式
(1)原理
-
一个工厂可以提供多个不同种类产品对象
-
不再是一一对应
-
多个位于不同产品等级结构中属于不同类型的具体产品时使用
- 抽象工厂定义产品等级结构
- 具体工厂定义产品族
-
-
重要定义:
-
产品等级结构: 产品等级结构即产品的继承结构
- 如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。
-
产品族: 产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品
- 如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中。
-
-
示例:
public static void Main(string[] args) { //使用平板工厂 //采购商要一台iPad和一台Tab Factory factory = new Factory_Pad(); Apple apple = factory.createAppleProduct(); //工厂父类中的创建苹果产品的方法 apple.AppleStyle(); Sumsung sumsung = factory.createSumsungProduct(); //工厂父类中的创建三星产品的方法 sumsung.BangziStyle(); //使用手机工厂 //采购商又要一台iPhone和一台Note2 factory = new Factory_Phone(); apple = factory.createAppleProduct(); apple.AppleStyle(); sumsung = factory.createSumsungProduct(); sumsung.BangziStyle(); Console.ReadKey(); }
(2)组成
- AbstractFactory:抽象工厂
- ConcreteFactory:具体工厂
- AbstractProduct:抽象产品
- Product:具体产品
(3)优缺点:
-
优点
-
抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。
-
由于这种隔离,更换一个具体工厂就变得相对容易。
-
所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。
-
应用抽象工厂模式可以实现高内聚低耦合的设计目的,因此抽象工厂模式得到了广泛的应用。
-
-
增加新的具体工厂和产品族很方便,
- 一个具体的工厂实现代表的是一个产品族,无须修改已有系统,符合“开闭原则”。
-
-
缺点
-
在添加新的产品对象(不同于现有的产品等级结构)时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽象工厂角色中规定了所有可能被创建的产品集合,要支持新种类的产品就意味着要对该接口进行扩展,而这将涉及到对抽象工厂角色及其所有子类的修改,显然会带来较大的不便。
-
开闭原则的倾斜性(增加新的工厂和产品族容易,增加新的产品等级结构麻烦)。
-
(4)适用环境
-
一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是重要的。
-
系统中有多于一个的产品族,而每次只使用其中某一产品族。与工厂方法模式的区别
-
属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。
-
系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
4、单例模式
(1)双重检查
public class Singleton {
private volatile static Singleton instance; //声明成 volatile
private Singleton (){}
public static Singleton getSingleton() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
-
可能存在的问题:
-
instance = new Singleton()
这句,这并非是一个原子操作(不可再分操作),事实上在 JVM 中这句话大概做了下面 3 件事情:-
I、给 instance 分配内存
-
II、调用 Singleton 的构造函数来初始化成员变量
-
III、将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)。
-
-
但是在 JVM 的即时编译器中存在指令重排序的优化。
-
也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。
-
如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
-
-
-
使用
volatile
关键字禁止指令重排序优化
(2)静态内部类单例(推荐使用)
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
//实现懒加载,只有在第一次调用此方法时才会去创建对象
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
5、建造者模式
(1)经典实现
-
原理
- 在产品实现外层套了一个统一的Builder去定义具体属性,然后通过Director去统一构造
-
组成
- Builder:抽象建造者
- ConcreteBuilder:具体建造者
- Director:指挥者
- Product:产品角色
-
优缺点
-
优点
- 良好的封装性, 使用建造者模式可以使客户端不必知道产品内部组成的细节;
- 建造者独立,容易扩展;
- 在对象创建过程中会使用到系统中的一些其它对象,这些对象在产品对象的创建过程中不易得到。
-
缺点
- 会产生多余的Builder对象以及Director对象,消耗内存;
- 对象的构建过程暴露。
-
(2)链式调用实现
-
每次对值操作完后返回对象本身
public Builder setTitle(CharSequence title) { P.mTitle = title; return this; }
二、结构型模式
-
结构型模式(Structural Pattern)描述如何将类或者对象结合在一起形成更大的结构
-
结构型模式可以分为两种
-
(1)类结构型模式
-
关心类的组合,由多个类可以组合成一个更大的系统
-
在类结构型模式中一般只存在继承关系和实现关系。
-
-
(2)对象结构型模式关心类与对象的组合
-
通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。
-
根据“合成复用原则”,在系统中尽量使用关联关系来替代继 承关系,因此大部分结构型模式都是对象结构型模式。
-
-
1、适配器模式
(1)思想:
-
把一个类的接口变换成客户端所期待的另一种接口(把被适配的类的API转换成为目标类的API),从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
-
当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法
-
这个过程对客户类是透明的,客户类并不直接访问适配者类
-
-
Android应用:ListView的getView等方法
- ListView继承AbsListView
- AbsListView中持有一个Adapter对象引用,通过对象适配器模式调用用户自定义的Adapter中的getView等方法
(2)两种结构的适配器模式
-
1⃣类的适配器模式
-
目的:扩展被适配的类(Adaptee)
-
思想:通过继承/实现Target,获取目标方法,实现Adaptee转换接口
-
目标(Target)角色:这就是所期待得到的接口。
- 注意:由于这里讨论的是类适配器模式,因此目标不可以是类。
-
源(Adaptee)角色:现在需要适配的接口。
-
适配器(Adaper)角色:适配器类是本模式的核心。
- 适配器把源接口转换成目标接口
public class Adapter extends Adaptee implements Target { /** * 由于源类Adaptee没有方法sampleOperation2() * 因此适配器补充上这个方法 */ @Override public void sampleOperation2() { //写相关的代码 } }
-
-
2⃣对象的适配器模式
-
目的:使Target能够调用Adaptee的API
-
思想:适配器持有一个Adaptee对象实例,使用委派关系连接到Adaptee类
public class Adapter { private Adaptee adaptee; public Adapter(Adaptee adaptee){ this.adaptee = adaptee; } /** * 源类Adaptee有方法sampleOperation1 * 因此适配器类直接委派即可 */ public void sampleOperation1(){ //在 this.adaptee.sampleOperation1(); } /** * 源类Adaptee没有方法sampleOperation2 * 因此由适配器类需要补充此方法 */ public void sampleOperation2(){ //写相关的代码 } }
-
(3)类适配器和对象适配器的选择
类适配器 | 对象适配器 |
---|---|
类适配器使用对象继承的方式,是静态的定义方式 | 对象适配器使用对象组合的方式,是动态组合的方式。 |
对于类适配器,由于适配器直接继承了Adaptee,使得适配器不能和Adaptee的子类一起工作,因为继承是静态的关系,当适配器继承了Adaptee后,就不可能再去处理 Adaptee的子类了 | 对于对象适配器,一个适配器可以把多种不同的源适配到同一个目标。换言之,同一个适配器可以把源类和它的子类都适配到目标接口。因为对象适配器采用的是对象组合的关系,只要对象类型正确,是不是子类都无所谓。 |
对于类适配器,适配器可以重定义Adaptee的部分行为,相当于子类覆盖父类的部分实现方法。 | 对于对象适配器,要重定义Adaptee的行为比较困难,这种情况下,需要定义Adaptee的子类来实现重定义,然后让适配器组合子类。虽然重定义Adaptee的行为比较困难,但是想要增加一些新的行为则方便的很,而且新增加的行为可同时适用于所有的源。 |
对于类适配器,仅仅引入了一个对象,并不需要额外的引用来间接得到Adaptee。 | 对于对象适配器,需要额外的引用来间接得到Adaptee。 |
(4)优缺点
-
优点:
-
更好的复用性
- 系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。
-
更好的扩展性
- 在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。
-
-
缺点:
- 过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现
2、外观模式
(1)思想:
-
外观类通过持有子系统对象实例,统一调度客户端对子系统的需求
-
在外观模式中,通常只需要一个外观类,并且此外观类只有一个实例,换言之它是一个单例类
-
能够用于有选择性地暴露方法
-
外观类中只暴露客户端可能用到的子系统的方法
-
子系统之间用于内部调度的方法不会暴露
-
(2)组成:
-
外观(Facade)角色 :
- 客户端可以调用这个角色的方法。此角色知晓相关的(一个或者多个)子系统的功能和责任。
- 在正常情况下,本角色会将所有从客户端发来的请求委派到相应的子系统去。
-
子系统(SubSystem)角色 :
- 可以同时有一个或者多个子系统。
- 每个子系统都不是一个单独的类,而是一个类的集合(如上面的子系统就是由ModuleA、ModuleB、ModuleC三个类组合而成)。
- 每个子系统都可以被客户端直接调用,或者被门面角色调用。
- 子系统并不知道门面的存在,对于子系统而言,门面仅仅是另外一个客户端而已。
(3)好处:
-
松散耦合
- 外观模式松散了客户端与子系统的耦合关系,让子系统内部的模块能更容易扩展和维护。
-
简单易用
- 外观模式让子系统更加易用,客户端不再需要了解子系统内部的实现,也不需要跟众多子系统内部的模块进行交互,只需要跟外观类交互就可以了。
-
更好的划分访问层次(有选择的暴露方法)
- 通过合理使用Facade,可以帮助我们更好地划分访问的层次。
- 有些方法是对系统外的,有些方法是系统内部使用的。把需要暴露给外部的功能集中到外观中,这样既方便客户端使用,也很好地隐藏了内部的细节。
3、装饰者模式
(1)思想:
- 装饰者模式以对客户透明的方式动态地通过新增接口实现类/子类的方式给一个对象附加上更多的责任
- 是继承关系的一种替代方案
(2)组成:
-
抽象构件(Component)角色:
- 给出一个抽象接口,以规范准备接收附加责任的对象。
-
具体构件(ConcreteComponent)角色:
- 定义一个将要接收附加责任的类。
-
装饰(Decorator)角色:
-
持有一个抽象构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
-
构造函数初始化
-
-
具体装饰(ConcreteDecorator)角色:
-
负责给构件对象“贴上”附加的责任。
-
可以获取到传入的对象示例,可以直接用或加以装饰
-
(3)使用示例
//客户端调用
public class Client {
public static void main(String[] args) {
TheGreatestSage sage = new Monkey();
// 第一种写法 单层装饰
TheGreatestSage bird = new Bird(sage);
TheGreatestSage fish = new Fish(bird);
// 第二种写法 双层装饰
//TheGreatestSage fish = new Fish(new Bird(sage));
fish.move();
}
}
(4)透明性要求
-
定义:装饰者模式对客户端的透明性要求程序不要声明一个ConcreteComponent类型的变量,而应当声明一个Component类型的变量
- 正确
TheGreatestSage sage = new Monkey(); TheGreatestSage bird = new Bird(sage);
- 错误
Monkey sage = new Monkey(); Bird bird = new Bird(sage);
-
半透明的装饰者模式
- 实际使用过程中,如果要对装饰者进行方法扩展,则必须要声明ConcreteDecorator类型的变量,从而可以调用ConcreteDecorator类中才有的方法
TheGreatestSage sage = new Monkey(); Bird bird = new Bird(sage); bird.fly();
-
透明和半透明两种区别:在于装饰角色的接口与抽象构件角色的接口是否完全一致。
-
透明的装饰者模式也就是理想的装饰者模式,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致。
-
如果装饰角色的接口与抽象构件角色接口不一致,也就是说装饰角色的接口比抽象构件角色的接口宽的话,装饰角色实际上已经成了一个适配器角色,这种装饰者模式也是可以接受的,称为“半透明”的装饰模式
-
(5)优缺点
-
优点:
-
装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
-
装饰模式允许系统动态决定“贴上”一个需要的“装饰”,或者除掉一个不需要的“装饰”。
-
继承关系则不同,继承关系是静态的,它在系统运行前就决定了。
-
-
通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
-
-
缺点:
- 但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。
4、代理模式
(1)思想:
- 给某一个对象提供一个代理,代理对象持有对原对象的引用,并根据客户端要求调用原对象方法。
- 客户端通过代理类实现对原对象的操作,使客户端和原对象解耦
(2)组成;
- Subject: 抽象主题角色
- Proxy: 代理主题角色
- RealSubject: 真实主题角色
(3)优缺点:
-
优点
- 给对象增加了本地化的扩展性,增加了存取操作控制
-
缺点
- 会产生多余的代理类
三、行为型模式
-
行为型模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化。
-
行为型模式分为两种:
-
(1)类行为型模式:
-
类的行为型模式使用继承关系在几个类之间分配行为
-
类行为型模式主要通过多态等方式来分配父类与子类的职责。
-
-
(2)对象行为型模式:
-
对象的行为型模式则使用对象的聚合关联关系来分配行为
-
对象行为型模式主要是通过对象关联等方式来分配两个或多个类的职责。根据“合成复用原则”,系统中要尽量使用关联关系来取代继承关系,因此大部分行为型设计模式都属于对象行为型设计模式。
-
-
1、观察者模式
(1)原理:
-
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
- 观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。
(2)Java对观察者模式的支持
- 在JAVA语言的java.util库里面,提供了一个
Observable类
以及一个Observer接口
-
Observer接口
只定义了一个方法,即update()
方法,当被观察者对象的状态发生变化时,被观察者对象的notifyObservers()
方法就会调用这一方法 -
Observable类
中使用Vector
(动态扩展大小的数组)来管理Observer
-
(3)组成
-
抽象主题(Subject)角色:
-
抽象主题角色把所有对观察者对象的引用保存在一个聚集(比如ArrayList对象)里,每个主题都可以有任何数量的观察者。
-
抽象主题提供一个接口,可以增加和删除观察者对象,抽象主题角色又叫做抽象被观察者(Observable)角色。
-
-
具体主题(ConcreteSubject)角色:
-
将有关状态存入具体观察者对象;
-
在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者(Concrete Observable)角色。
-
-
抽象观察者(Observer)角色:
- 为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。
-
具体观察者(ConcreteObserver)角色:
- 存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。
(4)推模型与拉模型
-
推模型(Observer的update方法传递的是主题对象中的部分参数)
- 主题对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。
-
拉模型(Observer的update方法传递的是主题对象自身)(自主性更强,推荐)
- 主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。
2、命令模式
(1)思想
- 命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象
-
可以将请求发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系
-
调用者持有命令的引用,命令持有接收者的引用
-
(2)组成
-
客户端(Client)角色:
- 创建请求者,接收者以及命令对象,执行具体逻辑。
-
命令(Command)角色:
- 声明了一个给所有具体命令类的抽象接口。
-
具体命令(ConcreteCommand)角色:
- 定义一个接收者和行为之间的弱耦合;实现execute()方法,负责调用接收者的相应操作。execute()方法通常叫做执行方法。
-
请求者(Invoker)角色:
- 负责调用命令对象执行请求,相关的方法叫做行动方法。
-
接收者(Receiver)角色:
- 负责具体实施和执行一个请求。任何一个类都可以成为接收者,实施和执行请求的方法叫做行动方法。
(3)优缺点
-
优点
-
更松散的耦合
- 命令模式使得发起命令的对象,和具体实现命令的对象完全解耦,也就是说发起命令的对象完全不知道具体实现对象是谁,也不知道如何实现。
-
更动态的控制
- 命令模式把请求封装起来,可以动态地对它进行参数化、队列化和日志化等操作,从而使得系统更灵活。
-
很自然的复合命令
- 命令模式中的命令对象能够很容易地组合成复合命令,也就是宏命令,从而使系统操作更简单,功能更强大。
-
更好的扩展性
- 由于发起命令的对象和具体的实现完全解耦,因此扩展新的命令就很容易,只需要实现新的命令对象,然后在装配的时候,把具体的实现对象设置到命令对象中,然后就可以使用这个命令对象,已有的实现完全不用变化。
-
-
缺点
- 使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。
3、迭代器模式
(1)思想
- 迭代器模式提供一个迭代器(Iterator)访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
-
把游走的任务放在迭代器上,而不是聚合上。
-
简化了聚合的接口和实现,也让责任各得其所
-
(2)组成
-
抽象迭代器(Iterator)角色:
- 此抽象角色定义出遍历元素所需的接口。
-
具体迭代器(ConcreteIterator)角色:
- 此角色实现了Iterator接口,并保持迭代过程中的游标位置。
-
聚集(Aggregate)角色:
- 此抽象角色给出创建迭代器(Iterator)对象的接口。
public abstract class Aggregate { /** * 工厂方法,创建相应迭代子对象的接口 */ public abstract Iterator createIterator(); }
- 迭代器模式要求聚集对象必须有一个工厂方法,也就是createIterator()方法,以向外界提供迭代器对象的实例。
-
具体聚集(ConcreteAggregate)角色:
- 实现了创建迭代器(Iterator)对象的接口,返回一个合适的具体迭代器实例。
-
客户端(Client)角色:
- 持有对聚集及其迭代器对象的引用,调用迭代子对象的迭代接口,也有可能通过迭代子操作聚集元素的增加和删除。
(3)优缺点
-
优点
-
①简化了遍历方式,对于对象集合的遍历,还是比较麻烦的,对于数组或者有序列表,我们尚可以通过游标来取得,但用户需要在对集合了解很清楚的前提下,自行遍历对象,但是对于hash表来说,用户遍历起来就比较麻烦了。而引入了迭代器方法后,用户用起来就简单的多了。
-
②可以提供多种遍历方式,比如说对有序列表,我们可以根据需要提供正序遍历,倒序遍历两种迭代器,用户用起来只需要得到我们实现好的迭代器,就可以方便的对集合进行遍历了。+
-
③封装性良好,用户只需要得到迭代器就可以遍历,而对于遍历算法则不用去关心。
-
-
缺点
- 对于比较简单的遍历(像数组或者有序列表),使用迭代器方式遍历较为繁琐,大家可能都有感觉,像ArrayList,我们宁可愿意使用for循环和get方法来遍历集合。
4、策略模式
(1)原理
-
对算法封装,把调用算法的责任(行为)和算法本身(行为实现)分割开来,委派给不同的对象管理
- 策略模式通常把一个系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。
(2)组成
-
环境(Context)角色:
- 持有一个Strategy的引用,即具有复杂多变行为的对象。
-
抽象策略(Strategy)角色:
- 这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
-
具体策略(ConcreteStrategy)角色:
- 包装了相关的算法或行为。
(3)使用示例
public static void main(String[] args) {
//选择并创建需要使用的策略对象
MemberStrategy strategy = new AdvancedMemberStrategy();
//创建环境
Price price = new Price(strategy);
//计算价格
double quote = price.quote(300);
System.out.println("图书的最终价格为:" + quote);
}
(4)深入理解
-
策略模式对多态的使用
- 通过让环境类持有一个抽象策略类(超类)的引用,在生成环境类实例对象时,让该引用指向具体的策略子类。再对应的方法调用中,就会通过Java的多态,调用对应策略子类的方法。从而可以相互替换,不需要修改环境类内部的实现。同时,在有新的需求的情况下,也只需要修改策略类即可,降低与环境类之间的耦合度。
-
策略模式的重心
- 策略模式的重心不是如何实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。
-
算法的平等性
-
策略模式一个很大的特点就是各个策略算法的平等性。对于一系列具体的策略算法,大家的地位是完全一样的,正因为这个平等性,才能实现算法之间可以相互替换。所有的策略算法在实现上也是相互独立的,相互之间是没有依赖的。
-
所以可以这样描述这一系列策略算法:策略算法是相同行为的不同实现。
-
-
运行时策略的唯一性
- 运行期间,策略模式在每一个时刻只能使用一个具体的策略实现对象,虽然可以动态地在不同的策略实现中切换,但是同时只能使用一个。
-
公有的行为
- 经常见到的是,所有的具体策略类都有一些公有的行为。这时候,就应当把这些公有的行为放到共同的抽象策略角色Strategy类里面。当然这时候抽象策略角色必须要用Java抽象类实现,而不能使用接口。
- 经常见到的是,所有的具体策略类都有一些公有的行为。这时候,就应当把这些公有的行为放到共同的抽象策略角色Strategy类里面。当然这时候抽象策略角色必须要用Java抽象类实现,而不能使用接口。
(5)优缺点
-
策略模式的优点
-
(1)策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免代码重复。
-
(2)使用策略模式可以避免使用多重条件(if-else)语句。多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重条件语句里面,比使用继承的办法还要原始和落后。
-
-
策略模式的缺点
-
(1)客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道算法或行为的情况。
-
(2)由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。
-
5、模版方法模式
(1)思想
-
子类可以置换掉父类的可变部分,但是子类却不可以改变模板方法所代表的顶级逻辑
- 通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制
-
实现:准备一个抽象类,在模版方法中实现对基本方法(或hookMethod())
-
基于继承的代码复用
-
代表具体逻辑步骤的方法称做基本方法(primitive method),一般是抽象方法,让子类实现
-
abstractMethod():子类必须实现,模版中只做抽象声明
-
hookMethod():子类选择实现,模版中有一个基本实现(或空实现)
-
-
将这些基本方法汇总起来的方法叫做模板方法(template method)
-
(2)组成
-
抽象模板(Abstract Template)角色
-
定义了一个或多个抽象操作,以便让子类实现。这些抽象操作叫做基本操作,它们是一个顶级逻辑的组成步骤。
-
定义并实现了一个模板方法。这个模板方法一般是一个具体方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也有可能调用一些具体方法。
-
-
具体模板(Concrete Template)角色
-
实现父类所定义的一个或多个抽象方法,它们是一个顶级逻辑的组成步骤。
-
每一个抽象模板角色都可以有任意多个具体模板角色与之对应,而每一个具体模板角色都可以给出这些抽象方法(也就是顶级逻辑的组成步骤)的不同实现,从而使得顶级逻辑的实现各不相同。
-
(3)优缺点:
-
优点
-
在父类中形式化地定义一个算法,而由它的子类来实现细节的处理,在子类实现详细的处理算法时并不会改变算法中步骤的执行次序。
-
模板方法模式是一种代码复用技术,它在类库设计中尤为重要,它提取了类库中的公共行为,将公共行为放在父类中,而通过其子类来实现不同的行为,它鼓励我们恰当使用继承来实现代码复用。
-
可实现一种反向控制结构,通过子类覆盖父类的钩子方法来决定某一特定步骤是否需要执行。
-
在模板方法模式中可以通过子类来覆盖父类的基本方法,不同的子类可以提供基本方法的不同实现,更换和增加新的子类很方便,符合单一职责原则和开闭原则。
-
-
缺点
- 需要为每一个基本方法的不同实现提供一个子类,如果父类中可变的基本方法太多,将会导致类的个数增加,系统更加庞大,设计也更加抽象,此时,可结合桥接模式来进行设计。