设计模式的分类
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
其实还有两类:并发型模式和线程池模式。
设计模式的5大原则
设计所要解决的主要问题,是如何高效率、高质量、低风险的应对各种各类变化,例如需求变更、软件升级等。设计的方式主要是提取抽象、隔离变化,有5大设计原则——“SOLID”,具体体现了这个思路。
S - 单一职责原则:
一个类只能有一个让它变化的原因。即,将不同的功能隔离开来,不要都混合到一个类中。
O - 开放封闭原则:
对扩展开放,对修改封闭。即,如果遇到需求变化,要通过添加新的类来实现,而不是修改现有的代码。这一点也符合单一职责原则。
L - Liskov原则:
子类可以完全覆盖父类。
I - 接口隔离原则:
每个接口都实现单一的功能。添加新功能时,要增加一个新接口,而不是修改已有的接口,禁止出现“胖接口”。符合单一职责原则和开放封闭原则。
D – 依赖倒置原则:
具体依赖于抽象,而非抽象依赖与具体。即,要把不同子类的相同功能抽象出来,依赖与这个抽象,而不是依赖于具体的子类。
总结这些设计原则可知,设计最终关注的还是“抽象”和“隔离”。面向对象的封装、继承和多态,还有每个设计模式,分析它们都离不开这两个词。
创建型模式
工厂方法模式(Factory Method)
工厂方法模式分为三种:
普通工厂模式
就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。首先看下关系图:
我们举一个发送邮件和短信的例子:
首先,创建二者的共同接口,
public interface Sender {
public void Send();
}
然后分别创建实现类,
public class MailSender implements Sender {
@Override
public void Send() {
System.out.println("this is mailsender!");
}
}
public class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("this is sms sender!");
}
}
最后创建工厂类,
public class SendFactory {
public Sender produce(String type) {
if ("mail".equals(type)) {
return new MailSender();
} else if ("sms".equals(type)) {
return new SmsSender();
} else {
System.out.println("请输入正确的类型!");
return null;
}
}
}
测试:
public class FactoryTest {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produce("sms");
sender.Send();
}
}
//输出:this is sms sender!
多个工厂方法模式
是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。关系图:
将上面的代码做下修改,改动下SendFactory类,如下:
public class SendFactory {
public produceMail(){
return new MailSender();
}
public produceSms(){
return new SmsSender();
}
}
测试类如下:
public class FactoryTest {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produceMail();
sender.Send();
}
}
静态工厂方法模式
将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。
public class SendFactory {
public static Sender produceMail(){
return new MailSender();
}
public static Sender produceSms(){
return new SmsSender();
}
}
测试类:
public class FactoryTest {
public static void main(String[] args) {
Sender sender = SendFactory.produceMail();
sender.Send();
}
}
小结
总体来说,工厂模式适合:凡是出现了大量的产品需要创建,并且具有共同的接口时,可以通过工厂方法模式进行创建。在以上的三种模式中,第一种如果传入的字符串有误,不能正确创建对象,第三种相对于第二种,不需要实例化工厂类,所以,大多数情况下,我们会选用第三种——静态工厂方法模式。
抽象工厂模式(Abstract Factory)
工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则。所以从设计角度考虑有一定的问题,如何解决?
就用到抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。因为抽象工厂不太好理解,我们先看看图:
首先定义一个接口:
public interface Sender {
public void Send();
}
然后创建两个实现类:
public class MailSender implements Sender {
@Override
public void Send() {
System.out.println("this is mailsender!");
}
}
public class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("this is sms sender!");
}
}
然后再定义一个接口和2个工厂类:
public interface Provider {
public Sender produce();
}
public class SendMailFactory implements Provider {
@Override
public Sender produce(){
return new MailSender();
}
}
public class SendSmsFactory implements Provider{
@Override
public Sender produce(){
return new SmsSender();
}
}
最后测试一下:
public class Test {
public static void main(String[] args) {
Provider provider = new SendMailFactory();
Sender sender = provider.produce();
sender.Send();
}
}
这个模式的好处就是,如果你现在想增加一个功能:发及时信息,则只需做一个实现类,实现Sender接口,同时做一个工厂类,实现Provider接口,就OK了,无需去改动现成的代码。这样做,拓展性较好。
单例模式(Singleton)
单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:
1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。
首先我们写一个简单的单例类:
public class Singleton {
/* 私有构造方法,防止被实例化 */
private Singleton() {};
private static Singleton instance = new Singleton();
/* 获取实例 */
public static Singleton getInstance() {
return instance;
}
}
建造者模式(Builder)
工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性。
其实建造者模式就是前面抽象工厂模式和最后的Test结合起来得到的。我们看一下代码:
还和前面一样,一个Sender接口,两个实现类MailSender和SmsSender。最后,建造者类如下:
public class Builder {
private List<Sender> list = new ArrayList<Sender>();
public void produceMailSender(int count){
for(int i=0; i<count; i++){
list.add(new MailSender());
}
}
public void produceSmsSender(int count){
for(int i=0; i<count; i++){
list.add(new SmsSender());
}
}
}
测试类:
Builder builder = new Builder();
builder.produceMailSender(10);
从这点看出,建造者模式将很多功能集成到一个类里,这个类可以创造出比较复杂的东西。所以与工厂模式的区别就是:工厂模式关注的是创建单个产品,而建造者模式则关注创建复合对象,多个部分。因此,是选择工厂模式还是建造者模式,依实际情况而定。
原型模式(Prototype)
原型模式虽然是创建型的模式,但是与工程模式没有关系,从名字即可看出,该模式的思想就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象。在Java中,复制对象是通过clone()实现的,先创建一个原型类:
public class Prototype implements Cloneable
{
public Object clone() throws CloneNotSupportedException
{
Prototype proto = (Prototype) super.clone();
return proto;
}
}
结构型模式
适配器模式(Adapter)
适配器模式:将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
想使用一个已经存在的类,而它的接口不符合要求,或者希望创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类协同工作。这个时候就可以使用是适配器模式,通过适配,则能协同工作。
在STL中,stack、queue、priority queue是通过借助deque的所提供的接口来实现。
假设客户(希望使用stack、queue、priority queue的程序员)希望能够使用stack数据结构,则需要提供一个能够实现stack功能的类给客户,该类应该提供能够实现stack的push与pop操作的接口给客户调用,并且该类中不应该含有其他与stack能够提供的功能无关的接口。但是,当程序中不存在这样的类时,则可以通过借助已经存在的类deque,定义一个stack类,使用deque所提供的接口来实现stack类应该提供的接口,即上文所述—将一个类的接口转换为客户需要的另外一个接口。
桥接模式(Bridge)
桥接模式:将抽象部分与它的实现相分离,使它们都可以独立地变化。
考虑装操作系统,有多种配置的计算机,同样也有多款操作系统。如何运用桥接模式呢?可以将操作系统和计算机分别抽象出来,让它们各自发展,减少它们的耦合度。当然了,两者之间有标准的接口。这样设计,不论是对于计算机,还是操作系统都是非常有利的。下面给出这种设计的UML图,其实就是桥接模式的UML图。
组合模式(Composite)
将对象组合成树形结构以表示”部分——整体“的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
要实现对简单对象和组合对象的一致性处理,关键点解在于设计一个抽象的组件类,让它可以代表组合对象和叶子对象。这样客户端就不用区分操作的是组合对象还是叶子对象(简单对象),尽管将它们全部当作组件对象进行统一的操作即可。
抽象组件(component)角色:为组合中的对象声明接口。在适当的情况下,实现所有类共有接口的缺省行为,同时声明一个接口用于访问和管理Component的子组件,如果有必要的时候,可在递归结构中定义一个接口,用于访问一个父部件,并在合适的情况下实现它。
叶子组件(Leaf)角色:在组合中表示叶节点对象,叶节点没有子节点,也就是它是最底层对象,在组合对象中用于定义图元对象的行为。
树枝组件(Composite)角色:也可称为组合组件,就是包含若干个叶子组件的组件,其定义了有子组件的那些组件的行为,用于存储子组件,同时定义了自身组件的特有的行为操作。
客户(Client)角色:使用Component接口操纵组合组件的对象。
public abstract class Component{
2: public abstract void Operation();
3: public void Add(Component component){}
4: public void Remove(Component component){}
5: public Component GetChild(int index){return null;}
6: }
7:
8: public class Leaf extends Component{
9: public void Operation(){
10: System.out.println("Leaf is Operating!");
11: }
12: }
13:
14: public class Composite extends Component{
15: private ArrayList<Component> components=null;
16: public void Operation(){
17: for (Component component : components) {
18: component.Operation();
19: }
20: }
21:
22: public void Add(Component component){
23: components.add(component);
24: }
25:
26: public void Remove(Component component){
27: components.remove(component);
28: }
29:
30: public Component GetChild(int index){
31: Component component=null;
32: if(index>=0||index<components.size()){
33: component=components.get(index);
34: }
35: return component;
36: }
37: }
38:
39: public class Client{
40: public static void main(String[] args){
41: Component leaf1=new Leaf();
42: Component leaf2=new Leaf();
43: Component composite=new Composite();
44: composite.Add(leaf1);
45: composite.Add(leaf2);
46:
47: composite.Operation();
48:
49: Component component=composite.GetChild(0);
50: component.Operation();
51: }
52: }
装饰者模式(Decorator)
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。
首先,抽象组件Component类定义了组件对象的接口,而具体组件ConcreteComponent实现了组件接口,是真正的组件。紧接着是关键的装饰器接口的定义,它继承了抽象组件接口,同时还持有一个组件接口对象的引用。之所以要继承组件接口,目的是为了让装饰对象与组件对象保持相同的接口,也就是类型一致,这样方便客户端无差别地操作组件对象和装饰对象,而持有一组件接口对象的主要原因是为了调用组件的核心职责,毕竟,装饰品的目的主要是给具体的组件对象添加额外职责,核心的功能还是交给组件对象本身来完成。综合二者,可以看出,装饰器接口的定义关键是为透明地给组件对象添加额外功能。
外观模式(Facade)
为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这个子系统更加容易使用。
将一个系统划分为若干个子系统有利于降低系统的复杂性,但是子系统过多,或者接口不够统一,也难免会造成用户程序与之交互的易用性。外观模式就是通过引入这么一个外观类,在这个类里面定义客户端想要的简单的方法,然后在这些方法的实现里面,由外观类再去分别调用系统内部中的各个子系统接口来实现功能,从而让客户端变得简单,只需要与外观类所定义好的简单接口打交道即可,也不用操心子系统间的互操作。这便是外观模式的巧妙之处。
外观模式的主要是目的不是给子系统添加新的功能接口,而是为了让外部减少与子系统的交互,松散耦合,从而让客户端能够更简单地使用子系统。
(我们以一个计算机的启动过程为例)
我们先看下实现类:
public class CPU {
public void startup(){
System.out.println("cpu startup!");
}
public void shutdown(){
System.out.println("cpu shutdown!");
}
}
public class Memory {
public void startup(){
System.out.println("memory startup!");
}
public void shutdown(){
System.out.println("memory shutdown!");
}
}
public class Disk {
public void startup(){
System.out.println("disk startup!");
}
public void shutdown(){
System.out.println("disk shutdown!");
}
}
public class Computer {
private CPU cpu;
private Memory memory;
private Disk disk;
public Computer(){
cpu = new CPU();
memory = new Memory();
disk = new Disk();
}
public void startup(){
System.out.println("start the computer!");
cpu.startup();
memory.startup();
disk.startup();
System.out.println("start computer finished!");
}
public void shutdown(){
System.out.println("begin to close the computer!");
cpu.shutdown();
memory.shutdown();
disk.shutdown();
System.out.println("computer closed!");
}
}
测试:
public class User {
public static void main(String[] args) {
Computer computer = new Computer();
computer.startup();
computer.shutdown();
}
}
如果我们没有Computer类,那么,CPU、Memory、Disk他们之间将会相互持有实例,产生关系,这样会造成严重的依赖,修改一个类,可能会带来其他类的修改,这不是我们想要看到的,有了Computer类,他们之间的关系被放在了Computer类里,这样就起到了解耦的作用,这就是外观模式。
享元模式(Flyweight)
享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。
其本质在于两方面:分离和共享。简单地说,分离的是对象状态中变与不变的部分,其中不变的部分设置为对象的内部状态,而随应用场景随之发生变化的部分为对象的外部状态;而共享指的就是对对象状态中不变部分的共享,因为内部状态不会随外部状态的改变而改变,从而也就具备了共享的条件,否则也就失去了共享的意义呢。再者,就是需要通过享元工厂来管理可共享的对象,也是各种具体的享元对象,享元工厂完全可以设计成单例,实现全局唯一性。
想像一下如下的场景:由淘宝商城提供的网店模板个数无论如何都是有限的,如果将每一个经营的网店都需要通过一个网店对象来表示的话,由于每一种网店模板都会被N多个商家一起采用,那么,很显然我们将需要数量巨大的网店对象实例。但是,由于采用相同一套网店模板的网店除了里面所展示的内容不一样或者使用权限不同一样以外,网店风格样式应该说是几乎完全一致的,除了一些可定制化的功能,比如logo等。所以,如果一个网店对应一个网店对象来表示的话,那么众多的网店对象中重复的内容将是非常可观的,也是完全没有必要的,因为有很大一部分是完全可以在使用同一套模板的网店对象里共享使用的,比如网店的风格、样式等,这些可以共享的部分其实就是享元模式中享元对象的内部状态;而网店所展示的内容由于与网店经营的产品有关,所以这部分基本上不会有相同的情况,也就是根据场景的不同,其内容也会随之变化,自然这些部分就是享元模式中享元对象的外部状态。
解决上述场景的网店对象数量巨大问题的较好途径便是将采用了同一网店模板的所有网店都用这个网店模板对象来表示,这样,需要的网店对象实例数量就与商城中所提供的网店模板数量保持一致,而由于网店模板数量有限,故产生的网店对象实例个数也不会太多,不足以影响系统的运行时效率。
接下来,让我们再简化一下上述场景,方便我们通过代码的方式来演绎场景过程吧。假设我们每个网店模板的内部状态只是一个简单的网店模板类型,比如有服装类网店模式和电器类网店模式等,而外部状态只有一个用户凭证,也就是网店经营者信息(现实情况肯定远比这样的假设要来得复杂,这里重点为了讲述享元模式本质。
public class User{
2: private String username;
3:
4: public User(String usernameString){
5: this.username=username;
6: }
7:
8: public String getUsername() {
9: return username;
10: }
11:
12: }
13:
14: public abstract class WebSite{
15: public abstract void Use(User user);
16: }
17:
18: public class ConcreteWebSite extends WebSite{
19: private String webType;
20:
21: public ConcreteWebSite(String webType){
22: this.webType=webType;
23: }
24:
25: public void Use(User user){
26: System.out.println("网站类型:"+webType+",经营者:"+user.getUsername());
27: }
28: }
29:
30:
31:
32: public class WebSiteFactory{
33: private HashMap<String, WebSite> websites=new HashMap<String, WebSite>();
34:
35: public WebSite GetWebSite(String webType){
36: WebSite webSite=websites.get(webType);
37: if(webSite!=null){
38: return webSite;
39: }else{
40: webSite=new ConcreteWebSite(webType);
41: websites.put(webType, webSite);
42: return webSite;
43: }
44: }
45:
46: public int getWebSiteNum(){
47: return websites.size();
48: }
49: }
50:
51: public class Client{
52: public static void main(String[] args){
53: WebSiteFactory webSiteFactory=new WebSiteFactory();
54:
55: WebSite webSite1=webSiteFactory.GetWebSite("服装类网店");
56: webSite1.Use(new User("zjb1"));
57: WebSite webSite2=webSiteFactory.GetWebSite("服装类网店");
58: webSite2.Use(new User("zjb2"));
59: WebSite webSite3=webSiteFactory.GetWebSite("服装类网店");
60: webSite3.Use(new User("zjb3"));
61:
62: WebSite webSite4=webSiteFactory.GetWebSite("电器类网店");
63: webSite4.Use(new User("zjb4"));
64: WebSite webSite5=webSiteFactory.GetWebSite("电器类网店");
65: webSite5.Use(new User("zjb5"));
66: WebSite webSite6=webSiteFactory.GetWebSite("电器类网店");
67: webSite6.Use(new User("zjb6"));
68:
69: System.out.println("得到所有网店总数为:"+webSiteFactory.getWebSiteNum());
70: //输出结果为2,说明使用同一网店模板的网店对象个数始终只有一个。这也达到了共享的目的
71: }
72: }
从上述示例代码中,我们可以看到:
网店类型作为享元对象的内部状态,而网店经营者用户凭证User作为享元对象的外部状态,前者不会随着外部状态的改变而改变,而后者却能够根据运用场景的不同而发生改变,比如User对象。对于享元工厂,我们完全可以将其单例化,全局只保留一个工厂实例。最后,从代码69行的输出来看,尽管同一网店模板有多个使用者,但是每一个网店模板对象实例数只会与使用的网店模板数一致,这样也就较好地达到了共享的目的。
代理模式(Proxy)
为其他对象提供一种代理以控制对这个对象的访问。
先来看看关系图:
代理(Proxy)角色:代理对象,实现与具体目标对象一致的接口,这样就可以使用代理来代替具体的目标对象。同时保存一个指向具体目标对象的引用,可以在需要的时候调用具体的目标对象。此外,亦可以控制对目标对象的访问,并通过代理对象来负责对目标对象的创建和销毁。
目标接口(Subject)角色:目标接口,定义代理和具体目标对象的接口,这样就可以在任何使用具体目标对象的地方使用代理对象。
具体目标对象(RealSubject)角色:具体的目标对象,真正实现目标接口功能。
客户(Client)角色:通过代理中间层与具体目标对象进行交互。
public abstract class Subject{
2: public abstract void Request();
3: }
4:
5: public class RealSubject extends Subject{
6: public void Request(){
7: //执行具体的功能处理
8: System.out.println("Request of RealSubject is done!");
9: }
10: }
11:
12: public class Proxy extends Subject{
13: private RealSubject realSubject=null;
14:
15: //通过参数完成对具体目标对象的创建,不过一般不推荐,因为客户端通常也无法直接构造具体目标对象
16: public Proxy(RealSubject realSubject){
17: this.realSubject=realSubject;
18: }
19:
20: //直接通过默认构造器来完成对具体目标对象的创建
21: public Proxy(){
22: this.realSubject=new RealSubject();
23: }
24:
25: public void Request(){
26: //在转调具体的目标对象之前,可以执行一些预处理
27:
28: realSubject.Request();
29:
30: //在转调具体的目标对象之后中,可以执行一些后处理
31: }
32: }
33:
34: public class Client{
35: public static void main(String[] args){
36: Subject subject=new Proxy();
37: subject.Request();
38: }
39: }
在现实的生活场景中,我们也可以随处可见代理模式的具体运用。比如,我们通常通过支票或者银行存单来完成对金钱的操作,换句话来说,就是它们在市场交易中作为现金的“代理人”。我们只需要通过它们就能完成对现金的兑现、存款等操作。这样,我们就能省去身上携带大笔现金的不便和不安全性呢,着实给我们日常生活带来一定程度的便利。
行为型模式
职责链模式
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
抽象处理者(Handler)角色:定义职责的接口,也就是在这里定义处理请求的方法,亦可以在这里实现后继链。
具体处理者(ConcreteHandler)角色:实现职责的类,在这个类中,实现对在它职责范围内请求的处理,如果处理不了,就继续转发请求给后继者。
客户端(client)角色:职责链的客户端,向链上的具体处理提交请求,让职责链负责处理。
public abstract class Handler{
2: protected Handler successor;
3: public void SetSuccessor(Handler handler){
4: this.successor=successor;
5: }
6:
7: public abstract void handleRequest(String request);
8: }
9:
10: public class ConcreteHandler1 extends Handler{
11: public void handleRequest(String request){
12:
13: //需要某些条件来判断当前请求是否为自己所能处理的范围,这里只是简单说明下
14: boolean someCondition=false;
15: if(someCondition){
16: System.out.println("ConcreteHandler1 handle request:"+request);
17: }else {
18: if(successor!=null){
19: //既然当前处理器不能处理请求,那么就直接将当前请求传递给它直接后继处理器,由其继续处理
20: successor.handleRequest(request);
21: }
22: }
23: }
24: }
25:
26: public class ConcreteHandler2 extends Handler{
27: public void handleRequest(String request){
28: boolean someCondition=false;
29: if(someCondition){
30: System.out.println("ConcreteHandler2 handle request:"+request);
31: }else {
32: if(successor!=null){
33: successor.handleRequest(request);
34: }else {
35: System.out.println("当前请求不能被所有处理者处理!");
36: }
37: }
38: }
39: }
40:
41: public class Client{
42: public static void main(String[] args){
43: ConcreteHandler1 handler1=new ConcreteHandler1();
44: ConcreteHandler2 handler2=new ConcreteHandler2();
45:
46: handler1.SetSuccessor(handler2);
47: handler2.SetSuccessor(null);
48:
49: handler1.handleRequest("just for test");
50: }
51: }
首先,我们通过定义完成职责的统一接口,即Handler类,在其中,我们不仅定义了完成职责的接口,而且也保存着其后继处理器引用句柄,目的就是当当前处理器无法处理当前请求操作时,交给这个后继处理器来处理,完成对请求传递过程,而不是停止于自己,让有能力负责的处理器来完成对请求的处理工作。而具体的处理器ConcreteHandler1和ConcreteHandler2都是实现了自己处理请求的业务逻辑功能,也就是hanlerRequest方法。而在客户端,我们根据业务需求将不同的具体处理器形成链式结构,然后直接将操作请求放置到处理链上进行处理即可,这样一来,客户端只需要知道职责链的第一个处理对象即可,因为需要通过它将操作请求传递到职责链中。当然,客户端也并不知道当前请求将由哪一个具体处理器接受处理,理想情况是不需要知道的。在这里,需要提醒一点是,虽然职责链在示例代码中是由客户端直接构建生成的,但是也完全可以由不同的情景上下文对象根据实际的业务要求来生成不同处理能力的职责链,直接交由客户端使用,而不需要客户端负责对职责链的创建工作。
命令模式
命令(Command)角色:定义命令的接口,声明执行的方法。
具体命令(ConcreteCommand)角色:命令接口实现对象,是“虚”的实现,通常它会持有命令的接收者,通过调用接收者相应的功能方法来执行当前命令所要完成的操作。
接收者(Receiver)角色:真正执行命令的对象。任何类都可以成为一个接收者,只要它能够实现命令要求实现的相应功能即可。
请求者(Invoker)角色:要求命令对象执行相关请求的对象,通常会持有命令对象,可以是多个命令对象。这是客户端真正触发命令并要求命令执行相应操作的入口点。
客户端(Client)角色:创建具体的命令对象,并设置命令对象的接收者。注意,这里的客户端并不是我们通常所指的客户端,而是指组装命令和接收者的地方,把这个Client称为装配者或者意义会更明了,真正使用命令的客户端是从Invoker来触发执行的,而不是从这个Client端命令的调用。