外观模式

外观模式

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

设计原则 #

  • 针对接口编程,而不是针对实现编程
  • 多用组合,少用继承
  • 为交互对象之间的松耦合而努力
  • 类应该对拓展开放,而对修改关闭 (开放-关闭原则)
  • 依赖抽象,而不依赖具体类 (依赖倒置原则)
  • "最少知识原则"——只和你的朋友交谈,不要让太多的类耦合在一起。

最少知识原则:定义 #

在设计系统中,不管是任何对象,都需要注意它所交互的类有哪些,并注意这些类是怎么交互的。

最少知识原则希望,在设计系统的过程中,不要让太多的类交杂在一起,免得修改系统中的一部分, 需要修改其他的部分。如果许多部分相互耦合,系统将会变得复杂、脆弱且不易于维护。

最少知识原则:如何遵循? #

设计系统的过程中,就任何对象而言,在对象的方法内,应该只调用属于一下范畴的方法:

  1. 对象本身的方法
  2. 被当作方法参数而传递进来的对象的方法
  3. 方法内创建或实例化的对象的方法
  4. 对象的任何组件(域、field)的方法

不要调用从上述方法中返回的对象支持的方法。

请看如下例:

1public float getTemp(){
2    Thermometer thermometer = station.getThermometer();
3    return thermometer.getTemperature();
4}

应该优化为:

1public float getTemp(){
2    return  station.getTemperature();
3}

getTemperature方法由station域提供。这样做的好处是,我们 不需要认识Thermometer对象了。让我们始终保持最小的朋友圈!

最少知识原则:缺点 #

是的,任何事情都是两面。最少知识原则使得系统耦合度降低,减轻了维护成本, 但同时禁止调用中间对象的方法,这不得不让我们制造更多的"包装"类来处理和 其他组件之间的沟通,这可能会导致开发难度增加。

让我们接着回到『外观模式』吧。

UML简图 #

外观模式

要点 #

  1. 外观模式中的Facade就是那个"密友",它封装了其他要用到的方法,满足了最少知识原则。
  2. 当要简化并统一一堆接口时,可以使用外观模式。
  3. 外观将客户从一个复杂的系统中解耦。
  4. 实现外观,需要将子系统组合进外观中,然后将具体的工作委托给子系统执行。
  5. 一个复杂的子系统,可以有多个外观。

示例代码 #

外观模式(Facade Pattern)是一种结构型设计模式,它提供了一个统一的接口来访问子系统中的一群接口。外观模式定义了一个高层接口,让子系统更容易使用。

在这个例子中,我们将创建一个简单的家庭影院系统,它包含了放映机、DVD播放器和音响系统等组件。我们将使用外观模式来提供一个简化的接口来控制这些组件,使得客户端不需要与各个子系统的复杂性直接交互。

首先,定义子系统的类:

 1// 放映机
 2class Projector {
 3    void on() {
 4        System.out.println("Projector on");
 5    }
 6    void off() {
 7        System.out.println("Projector off");
 8    }
 9}
10
11// DVD播放器
12class DvdPlayer {
13    void on() {
14        System.out.println("DVD Player on");
15    }
16    void play(String movie) {
17        System.out.println("Playing "" + movie + """);
18    }
19    void stop() {
20        System.out.println("Stop playing DVD");
21    }
22    void off() {
23        System.out.println("DVD Player off");
24    }
25}
26
27
28// 音响系统
29class SoundSystem {
30    void on() {
31        System.out.println("Sound system on");
32    }
33    void setVolume(int level) {
34        System.out.println("Setting volume to " + level);
35    }
36    void off() {
37        System.out.println("Sound system off");
38    }
39}

接下来,创建外观类HomeTheaterFacade,它提供了简单的方法来处理复杂的子系统操作:

 1class HomeTheaterFacade {
 2    private Projector projector;
 3    private DvdPlayer dvdPlayer;
 4    private SoundSystem soundSystem;
 5
 6    public HomeTheaterFacade(
 7            Projector projector, 
 8            DvdPlayer dvdPlayer, 
 9            SoundSystem soundSystem) {
10        this.projector = projector;
11        this.dvdPlayer = dvdPlayer;
12        this.soundSystem = soundSystem;
13    }
14
15    // 观看电影
16    void watchMovie(String movie) {
17        System.out.println("Get ready to watch a movie...");
18        projector.on();
19        soundSystem.on();
20        soundSystem.setVolume(10);
21        dvdPlayer.on();
22        dvdPlayer.play(movie);
23    }
24
25    // 结束观看
26    void endMovie() {
27        System.out.println("Shutting movie theater down...");
28        projector.off();
29        soundSystem.off();
30        dvdPlayer.stop();
31        dvdPlayer.off();
32    }
33}

最后,客户端代码可以通过外观类来简化对子系统的操作:

 1public class FacadePatternDemo {
 2    public static void main(String[] args) {
 3        Projector projector = new Projector();
 4        DvdPlayer dvdPlayer = new DvdPlayer();
 5        SoundSystem soundSystem = new SoundSystem();
 6        
 7        HomeTheaterFacade homeTheater 
 8            = new HomeTheaterFacade(projector, dvdPlayer, soundSystem);
 9        
10        homeTheater.watchMovie("The Shawshank Redemption");
11        homeTheater.endMovie();
12    }
13}

在这个示例中,HomeTheaterFacade类提供了一个简化的接口watchMovieendMovie,使得客户端不需要直接与放映机、DVD播放器和音响系统等子系统交互,从而简化了客户端的使用。这就是外观模式的魅力所在,它通过提供一个统一的接口来隐藏子系统的复杂性,使得子系统更加容易使用。

外观模式和宏命令的区别 #

感觉外观模式有点类似于命令模式的宏命令,都是通过接口组合抽取特定的行为组成 符合需求的逻辑,供客户调用。

不过,外观模式和命令模式的主要诉求不同:

  • 命令模式的诉求是将对接口的请求封装成对象,以便于参数化,甚至于结合队列来使用。
  • 外观模式的诉求是从一堆可以满足需求的接口中简化逻辑和依赖,简化开发。
  • 至于撤销操作,当然也可以通过外观模式来封装。

以下是chatgpt总结的外观模式和命令模式的宏命令的区别

外观模式(Facade Pattern)和命令模式中的“宏命令”(Macro Command)都可以用来简化客户端与一组操作之间的交互,但它们的设计目的、实现方式以及应用场景有所不同。

外观模式 #

外观模式提供了一个统一的高层接口,用来访问子系统中的一群接口。它的主要目的是隐藏系统的复杂性,并提供一个客户端可以访问系统的简单接口。外观模式通常在系统设计初期用于将各个复杂的子系统分离,以减少系统之间的依赖。在系统后期,外观模式可以用于简化客户端的调用,改善子系统之间的交互

  • 意图:提供一个统一的接口,用来访问子系统中的一群接口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
  • 应用场景:当你要为一个复杂子系统提供一个简单接口时;当客户端与多个子系统之间存在很大的依赖性时,引入外观模式以将这个子系统与客户端及其他子系统分离,可以提高子系统的独立性和可移植性。

命令模式的“宏命令” #

命令模式将请求封装成对象,从而允许用户使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。宏命令是命令模式的一种扩展,它可以执行一组命令,将多个命令封装为一个“宏”命令来执行。

  • 意图:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
  • 应用场景:当你需要参数化对象根据请求执行操作时;当你需要实现请求排队系统时;当你需要支持撤销操作时;当你需要将一组操作组合成一个大型操作时(即宏命令)。

区别 #

  • 设计目的:
    • 外观模式主要是为了隐藏系统的复杂性,提供一个简单的接口给外部调用。
    • 命令模式的宏命令则是为了将多个命令组合成一个命令,以便统一调用。
  • 实现方式:
    • 外观模式通常只有一个外观类,这个类将客户端的请求代理给相应的子系统处理。
    • 宏命令则是通过命令模式的结构,将多个命令对象组合成一个宏命令对象,这个宏命令也实现了命令接口,但它的execute方法会依次调用组合命令对象的execute方法。
  • 应用场景:
    • 外观模式适用于为复杂系统提供一个简单接口的场景。
    • 宏命令适用于需要将多个操作组合为一个操作执行,并可能支持撤销/重做等操作的场景。

总的来说,外观模式和命令模式的宏命令虽然都可以简化客户端的操作,但它们的关注点不同:外观模式关注于简化系统接口,命令模式的宏命令关注于将多个操作组合成单一操作,以便统一管理和调用。