设计模式

简介: 设计模式
+关注继续查看

软件开发的不变真理

CHANGE(改变)


不管当初软件设计得多好,一段时间之后,总是需要成长和改变,否则软件就会“死亡”

类之间的关系

  • IS-A(是一个)
  • HAS-A(有一个)
  • IMPLEMENTS(实现)

类图

https://www.jianshu.com/p/57620b762160

类图基础属性:

-表示private  

#表示protected

_下划线表示static  

斜体表示抽象  

继承:空心三角形+实线

image-20220319152909397.png

实现(比如实现接口):空心三角形+虚线

image-20220319152952866.png

依赖(对于两个相对独立的对象,当一个对象负责构造另一个对象的实例,或者依赖另一个对象的服务时,这两个对象之间主要体现为依赖关系。):虚线箭头

image-20220319153037571.png

关联(对于两个相对独立的对象,当一个对象的实例与另一个对象的一些特定实例存在固定的对应关系时,这两个对象之间为关联关系):实线箭头

image-20220319153118363.png

聚合(体现has-a的关系):空心菱形+实线箭头

image-20220319153159756.png

组合(contains-a的关系):实心菱形+实线箭头

image-20220319153240281.png

设计原则

  • 封装变化(找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起)
  • 针对接口编程,而不是针对实现编程
  • 多用组合,少用继承
  • 为交互对象之间的松耦合设计而努力
  • 类应该对扩展开放,对修改关闭(开闭原则)
  • 依赖抽象,不要依赖具体类(依赖倒置原则:不能让高层组件依赖低层组件,而且,不管高层或低层组件,二者都应该依赖于抽象)
  • 只和朋友交谈(最少知识原则:要减少对象之间的交互,只留下几个“密友”,不要让太多的类耦合在一起)
  • 别找我,我会找你(好莱坞原则,高层组件对待低层组件的方式是:别调用我们,我们会调用你)
  • 类应该只有一个改变的理由(单一责任原则,尽量让每个类保持单一责任,一个类应该只有一个引起变化的原因)

设计模式

策略模式

定义(意图)

策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户

动机(问题以及如何解决)

  1. 软件构建中,某些对象算法可能多种多样,经常改变,如果将它们都编码到对象中,会使得对象非常复杂,有时支持不适用的算法也会造成性能负担
  2. 在运行时透明地更改对象的算法,将算法与对象本身解耦,从而避免上述问题

适用性(适用场合)

当存在以下情况时使用策略模式:

  • 许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法
  • 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间/时间权衡的算法。当这些变体实现为一个算法的类层次时,可以使用策略模式
  • 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构
  • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的策略类中以代替这些条件语句

结构(类图)

image-20220317110540841.png

参与者(涉及的类和对象的责任和角色)

  • Strategy
    定义所有支持的算法的公共接口。Context使用这个接口来调用某ConcreteStrategy定义的算法
  • ConcreteStrategy
    具体策略,以Strategy接口实现某具体算法
  • Context
    Context表示上下文,它用一个ConcreteStrategy对象来配置,维护一个对Strategy对象的引用,可定义一个接口来让Strategy访问它的数据

协作(参与者如何合作)

  • Strategy和Context相互作用以实现选定的算法。当算法被调用时,Context可以将该算法所需要的所有数据都传递给该Strategy。或者,Context可以将自身作为一个参数传递给Strategy操作。这就让Strategy在需要时可以回调Context
  • Context将它的客户的请求转发给它的Strategy。客户通常创建并传递一个ConcreteStrtegy对象给该Context。这样,客户仅与Context交互。通常有一系列的ContextStrategy类可供客户从中选择

结果(采用模式后可能产生的好与坏结果)

好:

  • Strategy类层次为Context定义了一系列的可供重用的算法或行为
  • 将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展
  • 消除了一些条件语句。Strategy模式提供了用条件语句选择所需的行为以外的另一种选择。当不同的行为堆砌在一个类中时,很难避免使用条件语句来选择合适的行为。将行为封装在一个个独立的Strategy类中消除了这些条件语句
  • Strategy模式可以提供相同行为的不同实现。客户可以根据不同时间/空间权衡取舍要求从不同策略中进行选择

坏:

  • 客户必须了解不同的Strategy
  • 无论各个ConcreteStrategy实现的算法是简单还是复杂,它们都共享Strategy定义的接口。因此很可能某些ConcreteStrategy不会都用到所有通过这个接口传递给它们的信息;简单的ConcreteStrategy可能不使用其中的任何信息!这就意味着有时Context会创建和初始化一些永远不会用到的参数。若存在这样的问题,那么将需要在Strategy和Context之间更进行紧密的耦合
  • 增加了对象的数目


示例代码


场景1

有很多品种的鸭子,鸭子有两个行为:飞和叫,对于每个品种,具体的行为不一样。对于这种场景,可以用策略模式


我们需要定义鸭子为抽象类,具体的鸭子会继承这个抽象类,Duck抽象类中需要定义两个私有成员,表示飞和叫的策略,飞和叫的行为是接口,对应不同的具体实现,这些实现是可随时互相替换的,也就是只要改变具体鸭子类对应的策略即可


Duck抽象类
public abstract class Duck {
    private FlyBehavior flyBehavior;
    private QuackBehavior quackBehavior;

    public void performFly() {
        flyBehavior.fly();
    }

    public void performQuack() {
        quackBehavior.quack();
    }

    public abstract void display();

    public void setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }

    public void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }

    public Duck() {
    }
}


策略接口
public interface FlyBehavior {
    public void fly();
}


public interface QuackBehavior {
    public void quack();
}


具体的策略实现
public class FlyWithWings implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("I can fly with wings");
    }
}


public class Quack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("quack...quack");
    }
}


具体的鸭子实现
public class MallardDuck extends Duck {

    @Override
    public void display() {
        System.out.println("I am a com.my.StrategyPattern.Object.MallardDuck!");
    }

    public MallardDuck() {
        this.setFlyBehavior(new FlyWithWings());
        this.setQuackBehavior(new Quack());
    }

}


场景2

有一个游戏,这个游戏中玩家可以扮演很多种不同的角色,不同角色可以装备不同的武器,每种武器的攻击行为都不同,但角色和武器有对应关系,比如国王只能用剑,盗贼只能用小刀,但未来可能会出现新的武器可以使用


需要定义角色为抽象类,该抽象类中包含私有成员武器接口,武器接口可以对应不同的具体实现


角色抽象类
public abstract class Character {
    private WeaponBehavior weaponBehavior;

    public abstract void fight();

    public void setWeapon(WeaponBehavior weaponBehavior) {
        this.weaponBehavior = weaponBehavior;
    }

    public WeaponBehavior getWeaponBehavior() {
        return this.weaponBehavior;
    }

    public Character() {

    }
}


武器接口
public interface WeaponBehavior {
    public void useWeapon();
}


具体的角色实现
public class King extends Character {
    @Override
    public void fight() {
        this.getWeaponBehavior().useWeapon();
    }

    public King() {
        this.setWeapon(new SwordBehavior());
    }


}


具体的武器实现


public class SwordBehavior implements WeaponBehavior {
    @Override
    public void useWeapon() {
        System.out.println("I am a saber!");
    }
}



相关模式(此模式与其他模式之间的关系)

蝇量模式:Strategy对象通常是很好的轻量级对象


观察者模式


定义

观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新

动机

观察者模式中的关键对象是Subject和观察者。一个Subject可以有任意数目的依赖它的观察者。一旦Subject的状态发生改变,所有的观察者都得到通知。


这种交互也称为发布-订阅。Subject是通知的发布者。它发出通知时并不需知道谁是它的观察者。可以有任意数目的观察者订阅并接收通知

适用性

在以下任一情况下可以使用观察者模式:

  • 当一个抽象模型有两个方面,其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用
  • 当对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变
  • 当一个对象必须通知其他对象,而它又不能假定其他对象是谁。换言之,你不希望这些对象是紧耦合的

结构

image-20220317145155565.png

参与者

  • Subject
    Subject知道它的观察者。可以有任意多个观察者观察同一个Subject。Subejct提供注册和删除观察者对象的接口
  • Observer
    为那些在Subject发生改变时需获得通知的对象定义一个更新接口
  • ConcreteSubject(具体Subject)
    将有关状态存入各ConcreteObserver对象,当它的状态发生改变时,向它的各个观察者发出通知
  • ConcreteObserver
    维护一个指向ConcreteSubject对象的引用。存储有关状态,这些状态应与目标的状态保持一致。实现Observer的更新接口以使自身状态与目标的状态保持一致

协作

  • 当ConcreteSubject发生任何可能导致其观察者与其本身状态不一致的改变时,它将通知它的各个观察者
  • 在得到一个具体Subject的改变通知后,ConcreteObserver对象可向目标对象查询信息。ConcreteObserver使用这些信息以使它的状态与目标对象的状态一致

结果

好:

  • Subject和观察者之间的抽象耦合。一个Subject所知道的仅仅是它有一系列观察者,每个都符合抽象的Observer类的简单接口。Subject不知道任何一个观察者属于哪一个具体的类。这样Subejct和观察者之间的耦合是抽象的和最小的
  • 支持广播通信。不像通常的请求,Subject发送的通知不需指定它的接收者。通知被自动广播给所有已向该Subject对象登记的有关对象。Subject对象并不关心到底有多少对象对自己感兴趣;它唯一的责任就是通知它的各观察者。这给了你在任何时刻增加和删除观察者的自由。处理还是忽略一个通知取决于观察者。

坏:

  • 意外的更新。因为一个观察者并不知道其他观察者的存在,它可能对改变Subject的最终代价一无所知。在Subject上一个看似无害的操作可能会引起一系列对观察者以及依赖于这些观察者的那些对象的更新。此外,如果依赖准则的定义或维护不当,常常会引起错误的更新,这种错误通常很难捕获

实现

  • 创建Subejct到其观察者之间的映射。一个Subject对象跟踪它应通知的观察者的最简单的方式是显式地在Subject中保存对它们的引用。然而,当Subject很多而观察者较少时,这样存储可能代价太高。一个解决办法是用时间换空间,用一个关联查找机制(例如一个Hash表)来维护Subject到观察者的映射。这样一个没有观察者的Subject就不产生存储开销。但另一方面,这一方法增加了访问观察者的开销
  • 观察多个Subject。在某些情况下,一个观察者依赖于多个Subject可能是有意义的。在这种情况下,必须扩展Update接口以使观察者知道是哪一个Subject送来的通知。Subejct对象可以简单地将自己作为Update操作的一个参数,让观察者知道应去检查哪一个Subject
  • 谁触发更新?Subject和它的观察者依赖于通知机制来保持一致。但到底哪一个对象调用notify来触发更新?此时有两个选择:
    • 由subject对象的状态设定操作在改变subject对象的状态后自动调用notify。这种方法的优点是客户不需要记住要在subject对象上调用notify,缺点是多个连续的操作会产生多次连续的更新,可能效率较低
    • 由客户负责在适当的时候调用Notify。这样做的优点是客户可以在一系列的状态改变完成后再一次性地触发更新,避免了不必要的中间更新。缺点是给客户增加了触发更新的责任。
  • 对已删除Subject的悬挂引用。删除一个Subject时应注意不要在其观察者中遗留对该Subject的悬挂引用。一种避免悬挂引用的方法是,当一个Subject被删除时,让它通知它的观察者将对该Subject的引用复位。一般来说,不能简单地删除观察者,因为其他的对象可能会引用它们,或者也可能它们还在观察其他的Subejct
  • 在发出通知前确保目标的状态自身是一致的
  • 显式地指定感兴趣的改变。可以扩展Subject的注册接口,让各观察者注册为仅对特定事件感兴趣,以提高更新的效率。当一个事件发生时,Subject仅通知那些已注册为对该事件感兴趣的观察者
  • 封装复杂的更新语义。当Subject和观察者间的依赖关系特别复杂时,可能需要一个维护这些关系的对象。我们称这样的对象为更改管理器(ChangeManager)。它的目的是尽量减少观察者反映其Subject的状态变化所需的工作量。例如,如果一个操作涉及到对几个相互依赖的Subject进行改动,就必须保证仅有在所有的Subject都更改完毕后,才一次性地通知他们的观察者,而不是每个Subject都通知观察者ChangeManager有3个责任:
    • 它将一个Subejct映射到它的观察者并提供一个接口来维护这个映射。这就不需要由Subejct来维护对其观察者的引用,反之亦然
    • 它定义一个特定的更新策略
    • 根据一个Subject的请求,它更新所有依赖于这个Subject的观察者

image-20220317153558340.png

已知应用

  • MVC
  • jdk中内置了观察者模式
  • Swing
  • rmi

相关模式

桥接模式:通过封装复杂的更新语义,ChangeManager充当目标和观察者之间的中介者


单例模式:ChangeManager可使用单例模式来保证它是唯一的并且是可全局访问的


示例代码


场景

有设备一直在监测天气信息,比如温度和湿度,显示器需要监听天气信息的改变,如果天气信息改变,需要改变自身状态并显示最新的天气


需要定义天气信息为被观察者,显示器为观察者

JDK实现

JDK自带了观察者模式

public class WeatherData extends Observable {
    private float temperature;
    private float humidtity;
    private float pressure;

    public float getTemperature() {
        return temperature;
    }

    public float getHumidtity() {
        return humidtity;
    }

    public float getPressure() {
        return pressure;
    }

    public void setMessurements(float temperature, float humidtity, float pressure) {
        this.temperature = temperature;
        this.humidtity = humidtity;
        this.pressure = pressure;
        messurementsChanged();
    }

    public void messurementsChanged() {
        this.setChanged();
        this.notifyObservers();
    }
}


public interface DisplayElement {
    public void display();
}


public class CurrentConditionsDisplay implements DisplayElement, Observer {

    private float temperature;
    private float humidity;
    private float pressure;
    private Observable observable;

    @Override
    public void display() {
        System.out.println(StringUtils.join(new String[]{"current condition : temperature is ", String.valueOf(temperature),
                "; humidity is ", String.valueOf(humidity)}));
    }

    public CurrentConditionsDisplay(Observable observable) {
        this.observable = observable;
        observable.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {
        if (o instanceof WeatherData) {
            WeatherData weatherData = (WeatherData) o;
            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidtity();
            this.pressure = weatherData.getPressure();
            display();
        }

    }
}


自己实现观察者模式

需要定义主题接口,该接口需要定义3个方法:注册、移除、通知观察者


天气信息需要实现主题接口,并在内部维护观察者的集合


需要定义观察者接口,该接口需要定义update方法,用于改变观察者的状态


显示器需要实现观察者接口,并在内部维护它所订阅的主题


public interface Subject {
    public void registObserver(Observer observer);
    public void removeObserver(Observer observer);
    public void notifyObservers();
}


public class WeatherData implements Subject {

    private List<Observer> observerList;
    private float temperature;
    private float humidtity;
    private float pressure;

    public float getTemperature() {
        return temperature;
    }

    public float getHumidtity() {
        return humidtity;
    }

    public float getPressure() {
        return pressure;
    }

    public WeatherData() {
        observerList = new ArrayList<>();
    }

    @Override
    public void registObserver(Observer observer) {
        observerList.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        observerList.remove(observer);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observerList) {
            observer.update(temperature, humidtity, pressure);
        }
    }

    public void setMessurements(float temperature, float humidtity, float pressure) {
        this.temperature = temperature;
        this.humidtity = humidtity;
        this.pressure = pressure;
        messurementsChanged();
    }

    public void messurementsChanged() {
        notifyObservers();
    }
}


public interface Observer {
    public void update(float temperature, float humidity, float pressure);
}


public interface DisplayElement {
    public void display();
}


public class CurrentConditionsDisplay implements DisplayElement, Observer {

    private Subject weatherData;
    private float temperature;
    private float humidity;
    private float pressure;

    public CurrentConditionsDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.registObserver(this);
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        display();
    }

    @Override
    public void display() {
        System.out.println(StringUtils.join(new String[]{"current condition : temperature is ", String.valueOf(temperature),
                                            "; humidity is ", String.valueOf(humidity)}));
    }
}


装饰者模式


定义

装饰者模式动态地将责任附加到对象上。若要拓展功能,装饰者提供了比继承更有弹性的替代方案

动机

希望给某个对象而不是整个类添加一些功能

适用性

以下情况使用Decorator模式:

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
  • 处理那些可以撤销的职责
  • 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类

结构

image-20220317154853341.png

参与者

  • Component
    定义一个对象接口,可以给这些对象动态地添加职责
  • ConcreteComponent
    定义一个对象,可以给这个对象添加一些职责
  • Decorator
    维持一个指向Component对象的引用,并定义一个与Component接口一致的接口
  • ConcreteDecorator
    向组件添加职责

协作

Decorator将请求转发给它的Component对象,并有可能在转发请求前后执行一些附加的动作

结果

好:

  • 比继承更灵活。与对象的继承相比,装饰者模式提供了更加灵活的向对象添加职责的方式。可以用添加和分离的方法,用装饰在运行时刻增加和删除职责
  • 避免在层次结构高层的类有太多的特征。Decorator模式提供了一种“即用即付”的方式来添加职责。你可以定义一个简单的类,并且用Decorator类来给它逐渐地添加功能。可以从简单的部件组合出复杂的功能。

坏:

  • 产生许多小对象

实现

  • 接口的一致性。装饰者对象的接口必须和它所装饰的Component的接口是一致的

已知应用

java.io包

相关模式

  • 适配器模式:装饰者模式不同于适配器模式,因为装饰者仅改变对象的职责而不改变它的接口;而适配器将给对象一个全新的接口
  • 组合模式:可以将装饰者视为一个退化的、仅有一个组件的组合。然而,装饰者仅给对象添加一些额外的职责,它的目的不在于对象聚集
  • 策略模式:装饰者可以改变对象的外表;而策略模式使得你可以改变对象的内核,这是改变对象的两种途径


示例代码


场景1

需要读取一个文本文件,将每个字符转成小写输出,一种方法是创建一个类继承并重写FilterInputStream,Java的io包使用的是装饰者模式,FilterInputStream内部包含InputStream,实际上进行io还是调用InputStream进行io,但是可以进行特殊处理


public class LowerCaseInputStream extends FilterInputStream {
    public LowerCaseInputStream(InputStream in) {
        super(in);
    }

    @Override
    public int read() throws IOException {
        int c = super.read();
        return (c == -1 ? c : Character.toLowerCase((char)c));
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int result = super.read(b, off, len);
        for (int i = off; i < off + result; i++) {
            b[i] = (byte)Character.toLowerCase((char)b[i]);
        }
        return result;
    }
}


public class InputTest {
    public static void main(String[] args) throws IOException {
        int c;
        try {
            InputStream in = new LowerCaseInputStream(new BufferedInputStream(new FileInputStream("D:\\cs_file\\java\\DesignPattern\\test.txt")));
            while ((c = in.read()) >= 0) {
                System.out.print((char)c);
            }
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


场景2

奶茶店有很多不同品种的饮料,每种饮料有大中小三种规格,并且饮料都可以加料,比如加大豆等等,并且可以加多份,最后需要按照规格和加的料来算最后的价格为多少


需要定义饮料抽象类,该抽象类有cost抽象方法,用于计算价格。需要定义装饰者抽象类,装饰者抽象类继承了饮料抽象类,之后定义具体的饮料类继承自抽象类,定义具体的装饰者类,表示加料,装饰者类内部包含饮料类,装饰者类在调用cost方法时,会去调用内部包含饮料类的cost方法,并且加上料的钱返回


这样装饰者类可以一直装饰下去,不管用户的饮料加了多少种料,加了多少次,都可以无限套娃下去,最后调用cost方法算出的就是最终的价格


image.png


饮料抽象类
public abstract class Beverage {
    String description = "Unknown Beverage";

    final int TALL = 0;
    final int GRANDE = 1;
    final int VENTI = 2;

    private int size;

    public String getDescription() {
        return description;
    }

    public int getSize() {
        return this.size;
    }

    public void setSize(int size) {
        this.size = size;
    }

    public abstract double cost();
}


装饰者抽象类
public abstract class CondimentDecorator extends Beverage{
    @Override
    public abstract String getDescription();

    @Override
    public abstract int getSize();
}


具体饮料
public class HouseBlend extends Beverage{

    public HouseBlend(int size) {
        description = size + " House Blend Coffee";
        setSize(size);
    }

    @Override
    public double cost() {
        return 0.89;
    }
}


具体的装饰者(代表加的料)
public class Soy extends CondimentDecorator{

    Beverage beverage;

    public Soy(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public int getSize() {
        return beverage.getSize();
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Soy";
    }

    @Override
    public double cost() {
        int size = beverage.getSize();
        double cost = beverage.cost();
        if (size == beverage.TALL) {
            cost += 0.10;
        } else if (size == beverage.GRANDE) {
            cost += 0.15;
        } else if (size == beverage.VENTI) {
            cost += 0.20;
        }
        return cost;
    }
}


测试代码
public class StarbuzzCoffee {

    public static void main(String[] args) {
        Beverage beverage = new Espresso(0);
        System.out.println(beverage.getDescription() + "$" + beverage.cost());

        Beverage beverage2 = new DarkRoast(1);
        beverage2 = new Mocha(beverage2);
        beverage2 = new Mocha(beverage2);
        beverage2 = new Whip(beverage2);
        System.out.println(beverage2.getDescription() + "$" + beverage2.cost());

        Beverage beverage3 = new HouseBlend(2);
        beverage3 = new Soy(beverage3);
        beverage3 = new Mocha(beverage3);
        beverage3 = new Whip(beverage3);
        System.out.println(beverage3.getDescription() + "$" + beverage3.cost());
    }
}


工厂方法模式


定义

工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类


适用性

在下列情况下可以使用工厂方法模式:

  • 当一个类不知道它所必须创建的对象的类的时候
  • 当一个类希望由它的子类来指定它所创建的对象的时候
  • 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候

结构

image-20220317162610534.png

参与者

  • Product
    定义工厂方法所创建的对象的接口
  • ConcreteProduct
    实现Product接口
  • Creator
    • 声明工厂方法,该方法返回一个Product类型的对象。Creator也可以定义一个工厂方法的缺省实现,它返回一个缺省的ConcreteProduct对象
    • 可以调用工厂方法以创建一个Product对象
  • ConcreteCreator
    重定义工厂方法以返回一个ConcreteProduct实例

协作

Creator依赖于它的子类来定义工厂方法,返回一个适当的ConcreteProduct实例

结果

好:

  • 工厂方法不再将与特定应用有关的类绑定到你的代码中
  • 为子类提供钩子。用工厂方法在一个类的内部创建对象通常比直接创建对象更灵活,工厂方法可以给子类一个钩子以提供对象的扩展版本

坏:

  • 客户可能仅仅为了创建一个特定的ConcreteProduct对象,就不得不创建Creator的子类


相关模式

抽象工厂模式:抽象工厂模式常常用工厂方法来实现


模板方法模式:工厂方法通常在模板方法模式中被调用


原型模式:原型模式不需要创建Creator的子类,但它们通常要求一个针对Product类的Initialize操作。Creator使用Initialize来初始化对象




抽象工厂模式


定义

抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类


适用性

在以下情况下可以使用抽象工厂模式:

  • 一个系统要独立于它的产品的创建、组合和表示时
  • 一个系统要由多个产品系列中的一个来配置时
  • 当你要强调一系列相关的产品对象的设计以便进行联合使用时
  • 当你提供一个产品类库,而只想显示它们的接口而不是实现时

结构

image-20220317164518981.png

参与者

  • AbstractFactory
    声明一个创建抽象产品对象的操作接口
  • ConcreteFactory
    实现创建具体产品对象的操作
  • AbstractProduct
    为一类产品对象声明一个接口
  • ConcreteProduct
    定义一个将被相应的具体工厂创建的产品对象,实现AbstractProduct接口
  • Client
    仅使用由AbstractFactory和AbstractProduct类声明的接口

协作

  • 通常在运行时刻创建一个ConcreteFactory类的实例。这一具体的工厂创建具有特定实现的产品对象。为创建不同的产品对象,客户应使用不同的具体工厂
  • AbstractFactory将产品对象的创建延迟到它的ConcreteFactory子类

结果

好:

  • 分离了具体的类。抽象工厂模式帮助你控制一个应用创建的对象的类。因为一个工厂封装创建产品对象的责任和过程,它将客户与类的实现分离。客户通过它们的抽象接口操纵实例。产品的类名也在具体工厂的实现中被分离;它们不出现在客户代码中
  • 使得易于交换产品系列。一个具体工厂类在一个应用中仅出现一次——即在它初始化的时候。这使得改变一个应用的具体工厂变得很容易。它只需改变具体的工厂即可使用不同的产品配置,这是因为一个抽象工厂创建了一个完整的产品系列,所以整个产品系列会立刻改变。

坏:

  • 难以支持新种类的产品。这是因为AbstractFactory接口确定了可以被创建的产品集合。支持新种类的产品就需要扩展该工厂接口

实现

  • 将工厂作为单例。一个应用中一般每个产品系列只需要一个ConcreteFactory的实例。因此工厂通常最好实现为一个单例
  • 创建产品。AbstractFactory仅声明一个创建产品的接口,真正创建产品是由ConcreteProduct子类实现的。最通常的办法是为每一个产品定义一个工厂方法。一个具体的工厂将为每个产品重定义该工厂方法以生产指定产品。虽然这样的实现很简单,但它却要求每个产品系列都要有一个新的具体工厂子类,即使这些产品系列的差别很小
    如果有多个可能的产品系列,具体工厂也可以使用Prototype模式来实现。具体工厂使用产品系列中每一个产品的原型实例来初始化,且它通过复制它的原型来创建新的产品。在基于原型的方法中,使得不是每个新的产品系列都需要一个新的具体工厂类
  • 定义可扩展的工厂。AbstractFactory通常为每一种它可以生产的产品定义一个操作。产品的种类被编码在操作型构中。增加一种新的产品要求改变AbstractFactory的接口以及所有与它相关的类。一个更灵活但不太安全的设计是给创建对象的操作增加一个参数。该参数指定了将被创建的对象的种类。


相关模式

工厂方法模式:AbstractFactory类通常用工厂方法实现,但也可用原型模式实现


单例模式:一个具体的工厂通常是一个单例


代码示例

场景

有一个全美连锁的披萨店,用户可以点不同种类的披萨,对于每个分店,对应种类的披萨的制作方式都不同


需要定义一个抽象的工厂类,定义工厂方法用于返回披萨,定义抽象类披萨,具体的披萨继承该类


抽象工厂类
public abstract class PizzaStore {

    public Pizza orderPizza(String type) {
        Pizza pizza;
        pizza = createPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

    abstract Pizza createPizza(String type);
}


产品抽象类
public abstract class Pizza {
    String name;
    String dough;
    String sauce;
    ArrayList toppings = new ArrayList();


    void prepare() {
        System.out.println("Preparing " + name);
        System.out.println("Tossing dough...");
        System.out.println("Adding sauce...");
        System.out.println("Adding toppings: ");
        for (int i = 0; i < toppings.size(); i++) {
            System.out.println("    " + toppings.get(i));
        }
    }
    void bake() {
        System.out.println("Bake for 25 minutes at 350");
    }
    void cut() {
        System.out.println("Cutting the pizza into diagonal slices");
    }
    void box() {
        System.out.println("Place pizza in official PizzaStore box");
    }

    public String getName() {
        return name;
    }
}


具体工厂
public class NYPizzaStore extends PizzaStore{
    @Override
    Pizza createPizza(String type) {
        if ("cheese".equals(type)) {
            return new NYStyleCheesePizza();
        } else if ("veggie".equals(type)) {
            return new NYStyleVeggiePizza();
        } else if ("clam".equals(type)) {
            return new NYStyleClamPizza();
        } else if ("pepperoni".equals(type)) {
            return new NYStylePepperoniPizza();
        } else {
            return null;
        }
    }
}


具体产品
public class NYStyleCheesePizza extends Pizza{

    public NYStyleCheesePizza() {
        name = "NY Style Sauce and Cheese Pizza";
        dough = "Thin Crust Dough";
        sauce = "Marinara Sauce";

        toppings.add("Grated Reggiano Cheese");
    }
}


单例模式


定义

单例模式确保一个类只有一个实例,并提供一个全局访问点

动机

对一些类来说,只有一个实例是很重要的。如何才能保证一个类只有一个实例并且这个实例易于被访问?可以让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法

适用性

当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时

结构

image-20220317171711888.png

参与者

  • Singleton
    定义一个getInstance方法,允许客户访问它的唯一实例。getInstance是一个类方法
    Singleton可能负责创建它自己的唯一实例

协作

客户只能通过Singleton的getInstance操作访问一个Singleton的实例

结果

好:

  • 对唯一实例的受控访问。因为Singleton类封装了它的唯一实例,所以它可以严格地控制客户怎样以及何时访问它


相关模式

很多模式可以使用单例模式实现,如抽象工厂模式,Builder构造器模式,原型模式


代码示例

成员变量单例对象一定是私有,并且static和volatile修饰,获取实例方法需要双重判断,第一次判断单例对象是否被创建,如果没有创建,则需要锁住类,并且再判断一次单例对象是否被创建,第一次判断是为了提高效率,如果已经创建好了,就可以直接返回,第二次判断是为了安全,因为另一个线程可能已经创建好了单例对象并释放锁,如果不就行判断,会破坏单例


public class ChocolateBoiler {

    private boolean empty;

    private boolean boiled;

    private static volatile ChocolateBoiler chocolateBoiler;

    private ChocolateBoiler() {
        empty = true;
        boiled = false;
    }

    public static ChocolateBoiler getInstance() {
        if (chocolateBoiler == null) {
            synchronized (ChocolateBoiler.class) {
                if (chocolateBoiler == null) {
                    chocolateBoiler = new ChocolateBoiler();
                }
            }
        }
        return chocolateBoiler;

    }

}


命令模式


定义

命令模式将“请求”封装成对象,以便使用不同的请求、队列或日志来参数化其他对象。命令模式也支持可撤销的操作

动机

有时必须向某对象提交请求,但并不知道关于被请求的操作或请求的接受者的任何信息。命令模式可以将请求本身变成一个对象,这个对象可被存储并像其他的对象一样被传递。

适用性

当你有如下需求时,可使用命令模式:

  • 在不同的时刻指定、排列和执行请求
  • 支持取消操作。Command的Excute操作可在实施操作前将状态存储起来,在取消操作时这个状态用来消除该操作的影响。Command接口必须添加一个Unexecute操作,该操作取消上一次Execute调用的效果。执行的命令被存储在一个历史列表中。可通过向后和向前遍历这一列表并分别调用Unexecute和Execute来实现重数不限的“取消”和“重做”
  • 支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍。在Command接口中添加装载操作和存储操作,可以用来保持变动的一个一致的修改日志。从崩溃中恢复的过程包括从磁盘中重新读入记录下来的命令并用Execute操作重新执行它们

结构

image-20220317193708138.png

参与者

  • Command
    声明执行操作的接口
  • ConcreteCommand
    将一个接收者对象绑定于一个动作。调用接收者相应的操作,以实现Execute
  • Client
    创建一个具体命令对象并设定它的接收者
  • Invoker
    要求该命令执行这个请求
  • Receiver
    知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接收者

协作

  • Client创建一个ConcreteCommand对象并指定它的Receiver对象
  • 某Invoker对象存储该ConcreteCommand对象
  • 该Invoker通过调用Command对象的Execute操作来提交一个请求。若该命令是可撤销的,ConcreteCommand就在执行Execute操作之前存储当前状态以用于取消该命令
  • ConcreteCommand对象调用它的Receiver的一些操作以执行该请求

结果

好:

  • Command模式将调用操作的对象与知道如何实现该操作的对象解耦
  • 可将多个命令装配为一个复合命令。复合命令是组合模式的一个实例
  • 增加新的Command很容易,因为这无需改变已有的类

实现

  • 支持取消和重做。若Command提供Unexecute或Undo操作,就可支持取消和重做功能。为达到这个目的,ConcreteCommand类可能需要存储额外的状态信息。这个状态包括:
    • 接收者对象,它真正执行处理该请求的各操作
    • 接收者上执行操作的参数
    • 若处理请求的操作会改变接收者对象中的某些值,那么这些值也必须先存储起来。接收者还必须提供一些操作,以使该命令可将接收者恢复到它先前的状态
  • 若应用只支持一次取消操作,那么只需存储最近一次被执行的命令。而若要支持多级的取消和重做,就需要有一个已被执行命令的历史表列。该表列的最大长度决定了取消和重做的级数。历史表列存储了已被执行的命令序列。向后遍历该表列并逆向执行命令是取消它们的结果;向前遍历并执行命令是重执行它们

已知应用

日程安排、线程池、队列请求(工作队列等)


日志请求

相关模式

组合模式:可被用来实现宏命令


备忘录模式:可用来保持某个状态,命令用这一状态来取消它的效果


代码示例

场景

有一个智能家居遥控器,这个遥控器可以通过按不同按钮操作很多家具,并且可以撤回上一个操作指令,遥控器还有模式按钮,例如party模式,可以同时操作多个家具,也就是宏指令


需要定义命令接口,声明执行和撤回方法,具体的命令和宏命令都实现该接口,对于具体的命令类,内部包含它所控制的家具对象,对于宏命令,内部包含一个命令数组,执行或撤回宏命令即逐个执行或撤回数组中的命令


对于用户,即遥控器而言,需要通过容器维护一组命令,并且保存上一个指令,用于撤销操作


命令接口

命令接口定义执行和撤回方法

public interface Command {
    public void execute();
    public void undo();
}


具体设备
public class CeilingFan {
    public static final int HIGH = 3;
    public static final int MEDIUM = 2;
    public static final int LOW = 1;
    public static final int OFF = 0;
    String location;
    int speed;

    public CeilingFan(String location) {
        this.location = location;
        speed = OFF;
    }

    public void high() {
        speed = HIGH;
        System.out.println(location + " ceiling fan is on high");
    }

    public void medium() {
        speed = MEDIUM;
        System.out.println(location + " ceiling fan is on medium");
    }

    public void low() {
        speed = LOW;
        System.out.println(location + " ceiling fan is on low");
    }

    public void off() {
        speed = OFF;
        System.out.println(location + " ceiling fan is off");
    }

    public int getSpeed() {
        return speed;
    }

    public String getLocation() {
        return location;
    }
}


public class Light {

    private String location;

    public Light(String location) {
        this.location = location;
    }

    public void on() {
        System.out.println(location + " light is on");
    }

    public void off() {
        System.out.println(location + " light is off");
    }
}


具体命令
public class CeilingFanHighCommand implements Command {
    CeilingFan ceilingFan;
    int prevSpeed;

    public CeilingFanHighCommand(CeilingFan ceilingFan) {
        this.ceilingFan = ceilingFan;
    }

    @Override
    public void execute() {
        prevSpeed = ceilingFan.getSpeed();
        ceilingFan.high();
    }

    @Override
    public void undo() {
        if (prevSpeed == CeilingFan.HIGH) {
            ceilingFan.high();
        } else if (prevSpeed == CeilingFan.MEDIUM) {
            ceilingFan.medium();
        } else if (prevSpeed == CeilingFan.LOW) {
            ceilingFan.low();
        } else if (prevSpeed == CeilingFan.OFF) {
            ceilingFan.off();
        }
    }
}


public class LightOnCommand implements Command {

    Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.on();
    }

    @Override
    public void undo() {
        light.off();
    }
}


宏命令
public class MacroCommand implements Command {
    Command[] commands;

    public MacroCommand(Command[] commands) {
        this.commands = commands;
    }

    @Override
    public void execute() {
        for (int i = 0; i < commands.length; i++) {
            commands[i].execute();
        }
    }

    @Override
    public void undo() {
        for (int i = 0; i < commands.length; i++) {
            commands[i].undo();
        }
    }
}


操作器
public class RemoteControlWithUndo {
    Command[] onCommands;
    Command[] offCommands;
    Command undoCommand;

    public RemoteControlWithUndo() {
        onCommands = new Command[7];
        offCommands = new Command[7];

        Command noCommand = new NoCommand();
        for (int i = 0; i < 7; i++) {
            onCommands[i] = noCommand;
            offCommands[i] = noCommand;
        }
        undoCommand = noCommand;
    }

    public void setCommand(int slot, Command onCommand, Command offCommand) {
        onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
    }

    public void onButtonWasPushed(int slot) {
        onCommands[slot].execute();
        undoCommand = onCommands[slot];
    }

    public void offButtonWasPushed(int slot) {
        offCommands[slot].execute();
        undoCommand = offCommands[slot];
    }

    public void undoButtonWasPushed() {
        undoCommand.undo();
    }

    @Override
    public String toString() {
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("\n------ Remote Control ------\n");
        for (int i = 0; i < onCommands.length; i++) {
            stringBuffer.append("[slot " + i + "] " + onCommands[i].getClass().getName()
            + "     " + offCommands[i].getClass().getName() + '\n');
        }
        stringBuffer.append("[undo] " + undoCommand.getClass().getName());
        return stringBuffer.toString();
    }
}


测试代码
public class RemoteLoader {

    public static void main(String[] args) {
        RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();

        Light light = new Light("Living Room");
        TV tv = new TV("Living Room");
        Stereo stereo = new Stereo("Living Room");
        Hottub hottub = new Hottub();
        
        LightOnCommand lightOn = new LightOnCommand(light);
        StereoOnCommand stereoOn = new StereoOnCommand(stereo);
        TVOnCommand tvOn = new TVOnCommand(tv);
        HottubOnCommand hottubOn = new HottubOnCommand(hottub);
        LightOffCommand lightOff = new LightOffCommand(light);
        StereoOffCommand stereoOff = new StereoOffCommand(stereo);
        TVOffCommand tvOff = new TVOffCommand(tv);
        HottubOffCommand hottubOff = new HottubOffCommand(hottub);

        Command[] partyOn = {lightOn, stereoOn, tvOn, hottubOn};
        Command[] partyOff = {lightOff, stereoOff, tvOff, hottubOff};

        MacroCommand partyOnMacro = new MacroCommand(partyOn);
        MacroCommand partyOffMacro = new MacroCommand(partyOff);

        remoteControl.setCommand(0, partyOnMacro, partyOffMacro);

        System.out.println(remoteControl);
        System.out.println("--- Pushing Macro On ---");
        remoteControl.onButtonWasPushed(0);
        System.out.println("--- Pushing Macro Off ---");
        remoteControl.offButtonWasPushed(0);
    }
}


适配器模式


定义

适配器模式将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间

动机

一个应用可能会有一些类具有不同的接口并且这些接口互不兼容,这时我们可以使用适配器模式

适用性

以下情况使用适配器模式:

  • 你想使用一个已经存在的类,而它的接口不符合你的需求
  • 你想使用一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作

结构

对象适配器依赖于对象组合:

image-20220317200117937.png

参与者

  • Target
    定义Client使用的与特定领域相关的接口
  • Client
    与符合Target接口的对象协同
  • Adaptee
    定义一个已经存在的接口,这个接口需要适配
  • Adapter
    对Adaptee的接口与Target接口进行适配

协作

Client在Adapter实例上调用一些操作。接着适配器调用Adaptee的操作实现这个请求

结果

好:

  • 允许一个Adapter与多个Adaptee(即Adaptee本身以及它的所有子类)同时工作。Adapter也可以一次给所有的Adaptee添加功能

坏:

  • 使得重定义Adaptee的行为比较困难。这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身


已知应用

Java中可以将枚举适配到迭代器

相关模式

桥接模式:桥接模式的结构与对象适配器类似,但是桥接模式的出发点不同:桥接模式的目的是将接口部分和实现部分分离,从而对它们可以较为容易也相对独立地加以改变。而适配器模式则意味着改变一个已有对象的接口


装饰者模式:装饰者模式增强了其他对象的功能而同时又不改变它的接口。因此装饰者对应用程序的透明性比适配器要好。结果是装饰者decorator支持递归组合,而纯粹使用适配器

是不可能实现这一点的


代理模式:代理模式在不改变它的接口的条件下,为另一个对象定义了一个代理


代码示例

场景1

有不同种类的鸭子和火鸡,鸭子和火鸡的叫声不同,现在要让鸭子学火鸡叫,火鸡学鸭子叫,但因为物种不同,它们最终发出的声音还是不同的


需要定义鸭子和火鸡接口,声明叫方法,具体的鸭子和火鸡实现接口,需要定义鸭子和火鸡的适配器类,需要定义鸭子和火鸡适配器类,鸭子适配器类是为了把鸭子伪装成火鸡,所以它需要实现火鸡接口,成员变量是鸭子对象,在调用火鸡接口方法时,实际上还是调用的鸭子方法,火鸡适配器反之


image.png


不同接口
public interface Duck {
    public void quack();
    public void fly();
}


public interface Turkey {
    public void gobble();
    public void fly();
}


具体类
public class WildTurkey implements Turkey {
    @Override
    public void gobble() {
        System.out.println("Gobble gobble");
    }

    @Override
    public void fly() {
        System.out.println("I am flying a short distance");
    }
}


public class MallardDuck implements Duck {
    @Override
    public void quack() {
        System.out.println("Quack");
    }

    @Override
    public void fly() {
        System.out.println("I am flying");
    }
}


适配器类
public class DuckAdapter implements Turkey {
    private Duck duck;
    private Random rand;

    public DuckAdapter(Duck duck) {
        this.duck = duck;
        rand = new Random();
    }

    @Override
    public void gobble() {
        duck.quack();
    }

    @Override
    public void fly() {
        // 生成一个随机数,该随机数的范围为[0,5)
        if (rand.nextInt(5) == 0) {
            duck.fly();
        }
    }
}


public class TurkeyAdapter implements Duck {
    Turkey turkey;

    public TurkeyAdapter(Turkey turkey) {
        this.turkey = turkey;
    }

    @Override
    public void quack() {
        turkey.gobble();
    }

    @Override
    public void fly() {
        for (int i = 0; i < 5; i++) {
            turkey.fly();
        }
    }
}


测试代码
public class DuckTestDrive {
    public static void main(String[] args) {
        MallardDuck duck = new MallardDuck();

        WildTurkey turkey = new WildTurkey();
        Duck turkeyAdapter = new TurkeyAdapter(turkey);

        System.out.println("The Turkey says...");
        turkey.gobble();
        turkey.fly();

        System.out.println("\nThe Duck says...");
        testDuck(duck);

        System.out.println("\nThe TurkeyAdapter says...");
        testDuck(turkeyAdapter);
    }

    static void testDuck (Duck duck) {
        duck.quack();
        duck.fly();
    }
}


场景2

自己用迭代器模式实现Emeration和Iterator的互相适配


public class EnumerationIterator implements Iterator {
    Enumeration enumeration;

    public EnumerationIterator(Enumeration enumeration) {
        this.enumeration = enumeration;
    }

    @Override
    public boolean hasNext() {
        return enumeration.hasMoreElements();
    }

    @Override
    public Object next() {
        return enumeration.nextElement();
    }
}


public class IteratorToEnumeration implements Enumeration {

    private Iterator iterator;

    public IteratorToEnumeration(Iterator iterator) {
        this.iterator = iterator;
    }

    @Override
    public boolean hasMoreElements() {
        return iterator.hasNext();
    }

    @Override
    public Object nextElement() {
        return iterator.next();
    }
}


外观模式(门面模式)


定义

外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用

动机

将一个系统划分成为若干个子系统有利于降低系统的复杂性。一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小。达到该目标的途径之一就是引入一个外观对象,它为子系统中较一般的设施提供了一个单一而简单的界面

适用性

当遇到以下情况时使用外观模式:

  • 当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类。这使得子系统更具可重用性,也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。Facade可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以跃过facade层
  • 客户程序与抽象类的实现部分之间存在着很大的依赖性。引入facade将这个子系统与客户已经其他的子系统分离,可以提高子系统的独立性和可移植性
  • 当你需要构建一个层次结构的子系统时,使用facade模式定义子系统中每层的入口点。若子系统之间是相互依赖的,你可以让它们仅通过facade进行通讯,从而简化了它们之间的依赖关系

结构

image-20220317203032727.png

参与者

  • facade
    • 知道哪些子系统类负责处理请求
    • 将客户的请求代理给适当的子系统对象
  • Subsystem classes
    • 实现子系统的功能
    • 处理由facade对象指派的任务
    • 没有facade的任何相关信息

协作

  • 客户程序通过发送请求给facade的方式与子系统通讯,facade将这些消息转发给适当的子系统对象。尽管是子系统中的有关对象在做实际工作,但facade模式本身也必须将它的接口转换成子系统的接口
  • 使用facade的客户程序不需要直接访问子系统对象

结果

好:

  • 它对客户屏蔽子系统组件,因而减少了客户处理的对象的数目并使得子系统使用起来更加方便
  • 它实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件往往是紧耦合的。松耦合关系使得子系统的组件变化不会影响到它的客户。facade模式有助于建立层次结构系统,也有助于对对象之间的依赖关系分层。facade模式可以消除复杂的循环依赖关系。这一点在客户程序与子系统是分别实现的时候尤为重要

实现

  • 降低客户-子系统之间的耦合度。用抽象类实现Facade而它的具体子类对应于不同的子系统实现,这可以进一步降低客户与子系统的耦合度。这样,客户就能通过抽象的facade类接口与子系统通讯。这种抽象耦合关系使得客户不知道它使用的是子系统的哪一个实现


相关模式

抽象工厂模式:抽象工厂模式可以与外观模式一起使用以提供一个接口,这一接口可用来以一种子系统独立的方式创建子系统对象。


中介者模式:中介者模式与外观模式的相似之处是,它抽象了一些已有的类的功能。然而,中介者的目的是对对象之间的任意通讯进行抽象,通常用于集中不属于任何单个对象的功能。中介者模式中的对象知道中介者并与它通信,而不是直接与其他同类对象通信。相对而言,外观模式仅对子系统对象的接口进行抽象,从而使它们更容易使用;它并不定义新功能,子系统也不知道facade的存在


单例模式:通常来说,只需要一个facade对象,因此facade对象通常属于单例模式


代码示例


场景

有一个智能家居设备,这个设备负责家庭影院功能,只要用户按下家庭影院按钮,就会控制各种家具以达到影院模式


需要定义一个门面,该门面维护各类设备作为成员变量,对外提供开启和关闭影院模式的方法,具体方法就是操作各个设备


门面
public class HomeTheaterFacade {
    Amplifier amp;
    Tuner tuner;
    DvdPlayer dvd;
    CdPlayer cd;
    Projector projector;
    TheaterLights lights;
    Screen screen;
    PopcornPopper popper;

    public HomeTheaterFacade(Amplifier amp, Tuner tuner, DvdPlayer dvd, CdPlayer cd, Projector projector, TheaterLights lights, Screen screen, PopcornPopper popper) {
        this.amp = amp;
        this.tuner = tuner;
        this.dvd = dvd;
        this.cd = cd;
        this.projector = projector;
        this.lights = lights;
        this.screen = screen;
        this.popper = popper;
    }

    public void watchMovie(String movie) {
        System.out.println("Get ready to watch a movie...");
        popper.on();
        popper.pop();
        lights.dim(10);
        screen.down();
        projector.on();
        projector.wideScreenMode();
        amp.on();
        amp.setDvd(dvd);
        amp.setSurroundSound();
        amp.setVolume(5);
        dvd.on();
        dvd.play(movie);