外观模式
类型
结构型设计模式
核心思想
外观模式的核心思想是为一个复杂的系统提供一个简单易用的接口,以便于客户端能够更加方便地访问系统的功能。
概述
外观模式提供了一个简单的接口,用于访问系统的复杂子系统或类库。外观模式能够隐藏系统的复杂性,使得客户端调用更加方便简单,同时也能够降低系统之间的耦合度。外观模式通过将子系统的功能封装在一个统一的外观类中,来隐藏系统的实现细节。客户端只需要与外观类交互,而无需了解子系统内部的具体实现细节,这大大简化了客户端的编程工作。
场景
- 系统复杂度高,例如有多个子系统之间相互依赖,但客户端并不需要了解子系统的实现细节
- 需要对系统进行封装,保护系统的安全性和稳定性。可以作为一种保护性的封装手段,将核心功能封装到外观类中,提供简单的对外接口,减少系统被攻击或者篡改的风险。
- 需要提高系统的可维护性和可扩展性。外观模式可以作为一个抽象层,将实现细节和客户端调用解耦。比如添加新功能。无需修改之前客户端和其他子功能的已有代码。只需要修改抽象层的代码,增加新功能即可。
- 对于已有的结构复杂,耦合度高的屎山代码需要重构时,将客户端直接调用子系统的代码,改为调用外观类的接口,从而实现逐步重构
- 客户度需要与多个子系统进行数据通信,可以将多个耗时操作封装在一起,由外观类和各个子系统进行通信。将数据整合,减少客户端的通信次数,提高性能。
从不同阶段说
- 在设计初期,应该有意识将不同的两层分离,层与层之间建立外观
- 在开发阶段,子系统因为不断的重构变得越发复杂,出现越来越多小类,使外部调用变得困难,增加外观层简化客户端调用。
- 在维护一个遗留的大型系统时,如果新的需求必须依赖该系统,可以增加外观,使新需求和外观交互,外观和依赖的大型系统交互。
结构图
主要角色
-
外观(Facade):外观是客户端与系统之间的接口。它隐藏了系统的复杂性,提供了一个简单的接口给客户端使用。外观将客户端的请求委托给系统中的其他对象来完成任务。
-
子系统(Subsystem):子系统是外观的下层组成部分。它们是实际执行任务的对象。子系统可能由多个类或对象组成,并且可能包含其他子系统。
-
客户端(Client):客户端是使用外观模式的应用程序。客户端不直接与子系统交互,而是通过外观来调用子系统的功能。客户端不需要知道系统的内部细节,只需要了解外观的接口即可使用系统。
优点
- 简化客户端与系统之间的交互:客户端通过接口访问,不需要了解系统的内部复杂性
- 降低客户端和子系统间耦合度:客户端与接口交互、接口和子系统交互
- 促进系统的可维护性:因为其隐藏子系统的实现细节,修改子系统无需考虑客户端。
- 提高了系统安全性:因为不能直接访问系统的组件。
缺点
- 可能会增加系统的复杂性:因为多引入了一个类
- 可能会限制系统的灵活性:外观所暴露出来的接口是有限的,如果客户端需要访问其他的功能组合,只能通过修改外观类或者直接访问子系统组件完成。
- 不符合开闭原则:如果接口发生变化的时候需要修改接口类
demo(golang)
// Subsystem 子系统1:cpu
type CPU struct {
temperature int
}
func (c *CPU) start() {
c.temperature = 50
fmt.Println("CPU started, temperature:", c.temperature)
}
func (c *CPU) stop() {
c.temperature = 0
fmt.Println("CPU stopped")
}
// Subsystem 子系统2:内存
type Memory struct {
capacity int
}
func (m *Memory) load() {
m.capacity = 1024
fmt.Println("Memory loaded, capacity:", m.capacity)
}
func (m *Memory) unload() {
m.capacity = 0
fmt.Println("Memory unloaded")
}
// Subsystem 子系统3:磁盘
type Disk struct {
size int
}
func (d *Disk) read() {
d.size = 256
fmt.Println("Disk read, size:", d.size)
}
func (d *Disk) write() {
d.size = 128
fmt.Println("Disk write, size:", d.size)
}
// Facade 外观
type Computer struct {
cpu *CPU
memory *Memory
disk *Disk
}
func NewComputer() *Computer {
return &Computer{
cpu: &CPU{},
memory: &Memory{},
disk: &Disk{},
}
}
func (c *Computer) Start() {
c.cpu.start()
c.memory.load()
c.disk.read()
}
func (c *Computer) Stop() {
c.cpu.stop()
c.memory.unload()
c.disk.write()
}
// Client 客户端调用
func main() {
computer := NewComputer()
computer.Start()
fmt.Println("Using the computer...")
computer.Stop()
}
如果不使用外观模式,客户端在调用的时候,需要分别实例化cpu、内存和磁盘,然后分别进行调用。而使用外观模式只需要实例化一个computer结构体就ok了。
特别说明
外观模式体现了依赖倒转和迪米特法则
依赖倒转
高层模块不应该依赖于低层模块,而是应该依赖于抽象,即依赖于接口而不是具体实现。在外观模式中,客户端不直接依赖于系统的子模块,而是外观接口。实现了高层模块对地层模块的解耦
迪米特法则
一个对象应该尽可能少地了解其他对象的细节。在外观模式中,客户端只需要和外观类进行交互,无需了解子系统的复杂性和细节。