需求

【设计模式专题之外观模式】9-电源开关

题目描述

  • 小明家的电源总开关控制了家里的三个设备:空调、台灯和电视机。每个设备都有独立的开关密码,分别用数字1、2和3表示。即输入1时,空调关闭,输入2时,台灯关闭,输入3时,电视机关闭,当输入为4时,表示要关闭所有设备。请你使用外观模式编写程序来描述电源总开关的操作。

输入描述

  • 第一行是一个整数 N(1 <= N <= 100),表示后面有 N 行输入。
  • 接下来的 N 行,每行包含一个数字,表示对应设备的开关操作(1表示关闭空调,2表示关闭台灯,3表示关闭电视机,4表示关闭所有设备)。

输出描述

  • 输出关闭所有设备后的状态,当输入的数字不在1-4范围内时,输出Invalid device code.

输入示例

4
1
2
3
4

输出示例

Air Conditioner is turned off.
Desk Lamp is turned off.
Television is turned off.
All devices are off.

基本概念

外观模式Facade Pattern, 也被称为“门面模式”,是一种结构型设计模式,外观模式定义了一个高层接口,这个接口使得子系统更容易使用,同时也隐藏了子系统的复杂性。

门面模式可以将子系统关在“门里”隐藏起来,客户端只需要通过外观接口与外观对象进行交互,而不需要直接和多个子系统交互,无论子系统多么复杂,对于外部来说是隐藏的,这样可以降低系统的耦合度。

举个例子,假设你正在编写的一个模块用来处理文件读取、解析、存储,我们可以将这个过程拆成三部分,然后创建一个外观类,将文件系统操作、数据解析和存储操作封装在外观类中,为客户端提供一个简化的接口,如果后续需要修改文件处理的流程或替换底层子系统,也只需在外观类中进行调整,不会影响客户端代码。

基本结构

外观模式的基本结构比较简单,只包括“外观”和“子系统类”

image-20240104142426113

  • 外观类:对外提供一个统一的高层次接口,使复杂的子系统变得更易使用。
  • 子系统类:实现子系统的功能,处理外观类指派的任务。

简易实现

下面使用Java代码实现外观模式的通用结构

// 子系统A
class SubsystemA {
    public void operationA() {
        System.out.println("SubsystemA operation");
    }
}

// 子系统B
class SubsystemB {
    public void operationB() {
        System.out.println("SubsystemB operation");
    }
}

// 子系统C
class SubsystemC {
    public void operationC() {
        System.out.println("SubsystemC operation");
    }
}

// 外观类
class Facade {
    private SubsystemA subsystemA;
    private SubsystemB subsystemB;
    private SubsystemC subsystemC;

    public Facade() {
        this.subsystemA = new SubsystemA();
        this.subsystemB = new SubsystemB();
        this.subsystemC = new SubsystemC();
    }

    // 外观方法,封装了对子系统的操作
    public void facadeOperation() {
        subsystemA.operationA();
        subsystemB.operationB();
        subsystemC.operationC();
    }
}

// 客户端
public class Main {
    public static void main(String[] args) {
        // 创建外观对象
        Facade facade = new Facade();

        // 客户端通过外观类调用子系统的操作
        facade.facadeOperation();
    }
}

在上面的代码中,Facade 类是外观类,封装了对三个子系统SubSystem的操作。客户端通过调用外观类的方法来实现对子系统的访问,而不需要直接调用子系统的方法。

优缺点和使用场景

外观模式通过提供一个简化的接口,隐藏了系统的复杂性,降低了客户端和子系统之间的耦合度,客户端不需要了解系统的内部实现细节,也不需要直接和多个子系统交互,只需要通过外观接口与外观对象进行交互。

但是如果需要添加新的子系统或修改子系统的行为,就可能需要修改外观类,这违背了“开闭原则”。

外观模式的应用也十分普遍,下面几种情况都使用了外观模式来进行简化。

  • Spring框架是一个广泛使用外观模式的例子。Spring框架提供了一个大量的功能,包括依赖注入、面向切面编程(AOP)、事务管理等。Spring的ApplicationContext可以看作是外观,隐藏了底层组件的复杂性,使得开发者可以更轻松地使用Spring的功能。
  • JDBC提供了一个用于与数据库交互的接口。DriverManager类可以看作是外观,它简化了数据库驱动的加载和连接的过程,隐藏了底层数据库连接的复杂性。
  • Android系统的API中也使用了外观模式。例如,Activity类提供了一个外观,使得开发者可以更容易地管理应用的生命周期,而无需关心底层的事件和状态管理。

本题代码

import java.util.Scanner;

class AirConditioner {
    public void turnOff() {
        System.out.println("Air Conditioner is turned off.");
    }
}
class DeskLamp {
    public void turnOff() {
        System.out.println("Desk Lamp is turned off.");
    }
}
class Television {
    public void turnOff() {
        System.out.println("Television is turned off.");
    }
}

class PowerSwitchFacade {
    private DeskLamp deskLamp;
    private AirConditioner airConditioner;
    private Television television;

    public PowerSwitchFacade() {
        this.deskLamp = new DeskLamp();
        this.airConditioner = new AirConditioner();
        this.television = new Television();
    }

    public void turnOffDevice(int deviceCode) {
        switch (deviceCode) {
            case 1:
                airConditioner.turnOff();
                break;
            case 2:
                deskLamp.turnOff();
                break;
            case 3:
                television.turnOff();
                break;
            case 4:
                System.out.println("All devices are off.");
                break;
            default:
                System.out.println("Invalid device code.");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // 读取输入
        int n = scanner.nextInt();
        int[] input = new int[n];

        for (int i = 0; i < n; i++) {
            input[i] = scanner.nextInt();
        }

        // 创建电源总开关外观
        PowerSwitchFacade powerSwitch = new PowerSwitchFacade();

        // 执行操作
        for (int i = 0; i < n; i++) {
            powerSwitch.turnOffDevice(input[i]);
        }
    }
}

其他语言版本

C++

#include <iostream>
#include <vector>

class AirConditioner {
public:
    void turnOff() {
        std::cout << "Air Conditioner is turned off." << std::endl;
    }
};

class DeskLamp {
public:
    void turnOff() {
        std::cout << "Desk Lamp is turned off." << std::endl;
    }
};

class Television {
public:
    void turnOff() {
        std::cout << "Television is turned off." << std::endl;
    }
};

class PowerSwitchFacade {
private:
    DeskLamp deskLamp;
    AirConditioner airConditioner;
    Television television;

public:
    PowerSwitchFacade() {
    }

    void turnOffDevice(int deviceCode) {
        switch (deviceCode) {
            case 1:
                airConditioner.turnOff();
                break;
            case 2:
                deskLamp.turnOff();
                break;
            case 3:
                television.turnOff();
                break;
            case 4:
                std::cout << "All devices are off." << std::endl;
                break;
            default:
                std::cout << "Invalid device code." << std::endl;
        }
    }
};

int main() {
    // 读取输入
    int n;
    std::cin >> n;
    std::vector<int> input(n);

    for (int i = 0; i < n; i++) {
        std::cin >> input[i];
    }

    // 创建电源总开关外观
    PowerSwitchFacade powerSwitch;

    // 执行操作
    for (int i = 0; i < n; i++) {
        powerSwitch.turnOffDevice(input[i]);
    }

    return 0;
}

Python

class AirConditioner:
    def turn_off(self):
        print("Air Conditioner is turned off.")

class DeskLamp:
    def turn_off(self):
        print("Desk Lamp is turned off.")

class Television:
    def turn_off(self):
        print("Television is turned off.")

class PowerSwitchFacade:
    def __init__(self):
        self.desk_lamp = DeskLamp()
        self.air_conditioner = AirConditioner()
        self.television = Television()

    def turn_off_device(self, device_code):
        if device_code == 1:
            self.air_conditioner.turn_off()
        elif device_code == 2:
            self.desk_lamp.turn_off()
        elif device_code == 3:
            self.television.turn_off()
        elif device_code == 4:
            print("All devices are off.")
        else:
            print("Invalid device code.")

if __name__ == "__main__":
    # 读取输入
    n = int(input())
    input_data = [int(input()) for _ in range(n)]

    # 创建电源总开关外观
    power_switch = PowerSwitchFacade()

    # 执行操作
    for device_code in input_data:
        power_switch.turn_off_device(device_code)

Go

package main

import "fmt"

// AirConditioner 类
type AirConditioner struct{}

func (ac *AirConditioner) turnOff() {
	fmt.Println("Air Conditioner is turned off.")
}

// DeskLamp 类
type DeskLamp struct{}

func (dl *DeskLamp) turnOff() {
	fmt.Println("Desk Lamp is turned off.")
}

// Television 类
type Television struct{}

func (tv *Television) turnOff() {
	fmt.Println("Television is turned off.")
}

// PowerSwitchFacade 类
type PowerSwitchFacade struct {
	deskLamp       DeskLamp
	airConditioner AirConditioner
	television     Television
}

func (psf *PowerSwitchFacade) turnOffDevice(deviceCode int) {
	switch deviceCode {
	case 1:
		psf.airConditioner.turnOff()
	case 2:
		psf.deskLamp.turnOff()
	case 3:
		psf.television.turnOff()
	case 4:
		fmt.Println("All devices are off.")
	default:
		fmt.Println("Invalid device code.")
	}
}

func main() {
	var n int
	fmt.Scan(&n) // 读取输入

	input := make([]int, n)
	for i := 0; i < n; i++ {
		fmt.Scan(&input[i])
	}

	// 创建电源总开关外观
	powerSwitch := PowerSwitchFacade{}

	// 执行操作
	for i := 0; i < n; i++ {
		powerSwitch.turnOffDevice(input[i])
	}
}