设计模式——外观模式

224 阅读5分钟

外观模式

类型

结构型设计模式

核心思想

外观模式的核心思想是为一个复杂的系统提供一个简单易用的接口,以便于客户端能够更加方便地访问系统的功能。

概述

外观模式提供了一个简单的接口,用于访问系统的复杂子系统或类库。外观模式能够隐藏系统的复杂性,使得客户端调用更加方便简单,同时也能够降低系统之间的耦合度。外观模式通过将子系统的功能封装在一个统一的外观类中,来隐藏系统的实现细节。客户端只需要与外观类交互,而无需了解子系统内部的具体实现细节,这大大简化了客户端的编程工作。

场景

  1. 系统复杂度高,例如有多个子系统之间相互依赖,但客户端并不需要了解子系统的实现细节
  2. 需要对系统进行封装,保护系统的安全性和稳定性。可以作为一种保护性的封装手段,将核心功能封装到外观类中,提供简单的对外接口,减少系统被攻击或者篡改的风险。
  3. 需要提高系统的可维护性和可扩展性。外观模式可以作为一个抽象层,将实现细节和客户端调用解耦。比如添加新功能。无需修改之前客户端和其他子功能的已有代码。只需要修改抽象层的代码,增加新功能即可。
  4. 对于已有的结构复杂,耦合度高的屎山代码需要重构时,将客户端直接调用子系统的代码,改为调用外观类的接口,从而实现逐步重构
  5. 客户度需要与多个子系统进行数据通信,可以将多个耗时操作封装在一起,由外观类和各个子系统进行通信。将数据整合,减少客户端的通信次数,提高性能。

从不同阶段说

  1. 在设计初期,应该有意识将不同的两层分离,层与层之间建立外观
  2. 在开发阶段,子系统因为不断的重构变得越发复杂,出现越来越多小类,使外部调用变得困难,增加外观层简化客户端调用。
  3. 在维护一个遗留的大型系统时,如果新的需求必须依赖该系统,可以增加外观,使新需求和外观交互,外观和依赖的大型系统交互。 image.png

结构图

image.png

主要角色

  1. 外观(Facade):外观是客户端与系统之间的接口。它隐藏了系统的复杂性,提供了一个简单的接口给客户端使用。外观将客户端的请求委托给系统中的其他对象来完成任务。

  2. 子系统(Subsystem):子系统是外观的下层组成部分。它们是实际执行任务的对象。子系统可能由多个类或对象组成,并且可能包含其他子系统。

  3. 客户端(Client):客户端是使用外观模式的应用程序。客户端不直接与子系统交互,而是通过外观来调用子系统的功能。客户端不需要知道系统的内部细节,只需要了解外观的接口即可使用系统。

优点

  1. 简化客户端与系统之间的交互:客户端通过接口访问,不需要了解系统的内部复杂性
  2. 降低客户端和子系统间耦合度:客户端与接口交互、接口和子系统交互
  3. 促进系统的可维护性:因为其隐藏子系统的实现细节,修改子系统无需考虑客户端。
  4. 提高了系统安全性:因为不能直接访问系统的组件。

缺点

  1. 可能会增加系统的复杂性:因为多引入了一个类
  2. 可能会限制系统的灵活性:外观所暴露出来的接口是有限的,如果客户端需要访问其他的功能组合,只能通过修改外观类或者直接访问子系统组件完成。
  3. 不符合开闭原则:如果接口发生变化的时候需要修改接口类

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了。

特别说明

外观模式体现了依赖倒转和迪米特法则

依赖倒转

高层模块不应该依赖于低层模块,而是应该依赖于抽象,即依赖于接口而不是具体实现。在外观模式中,客户端不直接依赖于系统的子模块,而是外观接口。实现了高层模块对地层模块的解耦

迪米特法则

一个对象应该尽可能少地了解其他对象的细节。在外观模式中,客户端只需要和外观类进行交互,无需了解子系统的复杂性和细节。