在学习了装饰器模式如何动态地为对象添加功能后,我们来探讨外观模式。外观模式隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。
“为子系统中的一组接口提供一个统一的、高层的接口,使得子系统更容易使用。” (Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.)
想象一下启动一台家庭影院系统。你可能需要按顺序执行多个操作:打开电视机、打开DVD播放器、打开音响、调暗灯光、放下投影幕布等。每个设备都有自己的控制接口,操作起来很繁琐。
如果有一个“一键观影”的遥控器按钮(外观),按一下它,它内部会自动协调所有这些设备完成上述所有步骤。这个按钮就是外观,它简化了与复杂家庭影院子系统的交互。
watchMovie()
方法。外观模式的主要目的:
电脑开机:
powerOn()
接口。去餐厅点餐:
汽车的一键启动:
银行的客服电话或柜台:
外观模式通常包含以下角色:
Facade
对象。Facade
对象提供的简化方法。Facade
对象接收到请求后,会根据需要调用一个或多个子系统类的方法来完成任务。Facade
可能会对子系统的结果进行组合或转换,然后返回给客户端。客户端通常只与 Facade
交互,但如果需要,也可以直接访问子系统类(外观模式并不阻止直接访问子系统)。
优点:
缺点:
让我们以一个简化的计算机启动过程为例。计算机启动涉及CPU、内存、硬盘等多个子系统。
// cpu.go (Subsystem Class)
package computer
import "fmt"
type CPU struct{}
func (c *CPU) Freeze() {
fmt.Println("CPU: Freezing...")
}
func (c *CPU) Jump(position int64) {
fmt.Printf("CPU: Jumping to address %#x\n", position)
}
func (c *CPU) Execute() {
fmt.Println("CPU: Executing commands...")
}
// memory.go (Subsystem Class)
package computer
import "fmt"
type Memory struct{}
func (m *Memory) Load(position int64, data []byte) {
fmt.Printf("Memory: Loading data to address %#x (data length: %d)\n", position, len(data))
// 实际加载数据到内存
}
// hard_drive.go (Subsystem Class)
package computer
import "fmt"
type HardDrive struct{}
func (hd *HardDrive) Read(lba int64, size int) []byte {
fmt.Printf("HardDrive: Reading %d bytes from LBA %d\n", size, lba)
// 实际从硬盘读取数据
return []byte("boot_sector_data_from_hdd")
}
// CPU.java (Subsystem Class)
package com.example.computer.subsystems;
public class CPU {
public void freeze() {
System.out.println("CPU: Freezing...");
}
public void jump(long position) {
System.out.printf("CPU: Jumping to address %#x%n", position);
}
public void execute() {
System.out.println("CPU: Executing commands...");
}
}
// Memory.java (Subsystem Class)
package com.example.computer.subsystems;
public class Memory {
public void load(long position, byte[] data) {
System.out.printf("Memory: Loading data to address %#x (data length: %d)%n", position, data.length);
// 实际加载数据到内存
}
}
// HardDrive.java (Subsystem Class)
package com.example.computer.subsystems;
public class HardDrive {
public byte[] read(long lba, int size) {
System.out.printf("HardDrive: Reading %d bytes from LBA %d%n", size, lba);
// 实际从硬盘读取数据
return "boot_sector_data_from_hdd".getBytes();
}
}
// computer_facade.go (Facade Class)
package computer
import "fmt"
const BOOT_ADDRESS int64 = 0x7C00
const BOOT_SECTOR_LBA int64 = 0
const SECTOR_SIZE int = 512
// ComputerFacade 外观类
type ComputerFacade struct {
cpu *CPU
memory *Memory
hardDrive *HardDrive
}
func NewComputerFacade() *ComputerFacade {
return &ComputerFacade{
cpu: &CPU{},
memory: &Memory{},
hardDrive: &HardDrive{},
}
}
// Start 提供一个简化的启动接口
func (cf *ComputerFacade) Start() {
fmt.Println("ComputerFacade: Starting computer...")
cf.cpu.Freeze() // 1. CPU 准备
bootData := cf.hardDrive.Read(BOOT_SECTOR_LBA, SECTOR_SIZE) // 2. 从硬盘读取引导扇区
cf.memory.Load(BOOT_ADDRESS, bootData) // 3. 加载引导扇区到内存
cf.cpu.Jump(BOOT_ADDRESS) // 4. CPU 跳转到引导地址
cf.cpu.Execute() // 5. CPU 开始执行
fmt.Println("ComputerFacade: Computer started successfully.")
}
// Shutdown (可以添加其他简化操作)
func (cf *ComputerFacade) Shutdown() {
fmt.Println("ComputerFacade: Shutting down computer...")
// 复杂的关机流程...
fmt.Println("ComputerFacade: Computer shut down.")
}
// ComputerFacade.java (Facade Class)
package com.example.computer;
import com.example.computer.subsystems.CPU;
import com.example.computer.subsystems.HardDrive;
import com.example.computer.subsystems.Memory;
public class ComputerFacade {
private static final long BOOT_ADDRESS = 0x7C00L;
private static final long BOOT_SECTOR_LBA = 0L;
private static final int SECTOR_SIZE = 512;
private CPU cpu;
private Memory memory;
private HardDrive hardDrive;
public ComputerFacade() {
this.cpu = new CPU();
this.memory = new Memory();
this.hardDrive = new HardDrive();
}
// start 提供一个简化的启动接口
public void startComputer() {
System.out.println("ComputerFacade: Starting computer...");
cpu.freeze(); // 1. CPU 准备
byte[] bootData = hardDrive.read(BOOT_SECTOR_LBA, SECTOR_SIZE); // 2. 从硬盘读取引导扇区
memory.load(BOOT_ADDRESS, bootData); // 3. 加载引导扇区到内存
cpu.jump(BOOT_ADDRESS); // 4. CPU 跳转到引导地址
cpu.execute(); // 5. CPU 开始执行
System.out.println("ComputerFacade: Computer started successfully.");
}
// shutdown (可以添加其他简化操作)
public void shutdownComputer() {
System.out.println("ComputerFacade: Shutting down computer...");
// 复杂的关机流程...
System.out.println("ComputerFacade: Computer shut down.");
}
}
// main.go (示例用法)
/*
package main
import (
"./computer"
"fmt"
)
func main() {
fmt.Println("--- Client: Using Computer Facade ---")
computer := computer.NewComputerFacade()
computer.Start() // 客户端只需要调用一个简单的方法
fmt.Println("\n--- Client: Later, shutting down computer ---")
computer.Shutdown()
// 如果需要,客户端仍然可以直接访问子系统(不推荐,除非外观未提供所需功能)
// fmt.Println("\n--- Client: Directly accessing subsystem (not typical use of facade) ---")
// cpu := &computer.CPU{}
// cpu.Execute()
}
*/
// Main.java (示例用法)
/*
package com.example;
import com.example.computer.ComputerFacade;
// import com.example.computer.subsystems.CPU; // For direct access example
public class Main {
public static void main(String[] args) {
System.out.println("--- Client: Using Computer Facade ---");
ComputerFacade computer = new ComputerFacade();
computer.startComputer(); // 客户端只需要调用一个简单的方法
System.out.println("\n--- Client: Later, shutting down computer ---");
computer.shutdownComputer();
// 如果需要,客户端仍然可以直接访问子系统(不推荐,除非外观未提供所需功能)
// System.out.println("\n--- Client: Directly accessing subsystem (not typical use of facade) ---");
// CPU cpu = new CPU();
// cpu.execute();
}
}
*/
外观模式和适配器模式都用于封装其他对象,但目的不同:
外观模式 (Facade):
适配器模式 (Adapter):
简单来说:
外观模式通过提供一个统一的接口来封装子系统中一组复杂的接口,从而简化了客户端与子系统的交互。它有效地降低了耦合,提高了系统的可维护性和易用性。当你面对一个复杂的系统,并希望为客户端提供一个更简单、更直接的访问方式时,外观模式是一个非常好的选择。
记住它的核心:提供高层统一接口,简化子系统访问。