深圳幻海软件技术有限公司 欢迎您!

外观模式,一个每天都在用,却被多数人在面试中忽视的模式

2023-02-28

大家好,这里是每周都在陪你一起进步的网管~!今天继续设计模式学习之旅,这次咱们分享个大家每个人在写代码时都用过,但面试时经常忽视它的设计模式--外观模式,我们一起来看看吧。现代的软件系统都非常复杂,尽管我们已经想尽一切方法将其“分而治之”,把一个系统划分为好几个较小的子系统了,但是仍然可能会存在这样

大家好,这里是每周都在陪你一起进步的网管~!今天继续设计模式学习之旅,这次咱们分享个大家每个人在写代码时都用过,但面试时经常忽视它的设计模式--外观模式,我们一起来看看吧。

现代的软件系统都非常复杂,尽管我们已经想尽一切方法将其“分而治之”,把一个系统划分为好几个较小的子系统了,但是仍然可能会存在这样的问题:子系统内有非常多的类,客户端往往需要和许多对象打交道之后才能完成想要完成的功能。

模式的由来

在我们的生活中医院就是这样的。一般的医院都会分为挂号、门诊、化验、收费、取药等。看病的病人要想治好自己的病(相当于一个客户端想要实现自己的功能)就要和医院的各个部门打交道。首先,病人需要挂号,然后门诊,如果医生要求化验的话,病人就要去化验,然后再回到门诊室,最后拿药,经过一系列复杂的过程后才能完成看病的过程。如下图所示:

如果我们在医院设立一个接待员的话,病人只负责和接待员接触,由接待员负责与医院的各个部门打交道,如下图所示:

医院设立的接待员的角色就是我们今天要介绍的外观模式,系统通过引入外观模式让需要调用多个子系统各自部分的功能接口以完成的需求,变为调用方只需要跟外观提供的统一功能进行交互即可。

引入外观模式带来的变化

模式定义

外观模式又称为门面模式,它是一种结构型模式。引入外观模式后调用方与多个子系统的通信必须通过一个统一的外观对象进行,外观模式为子系统中的功能接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这些子系统更加容易使用。

外观模式的结构

外观模式的结构其实很简单,用一个UML图就能描述清楚外观模式里拥有哪些角色以及它们各自的特点,下面我们看一下外观模式的类结构。

外观模式编程实现

下面我们用Go代码实现一个外观模式。

这个例子里我们把电脑拥有的CPU、RAM内存和硬盘视为子系统,调用方想启动电脑就得分别启动这三个子系统才行,所以我们在子系统上增加一个外观对象,让调用方直接调用外观对象,由外观对象再去分别对接子系统最终完成电脑的启动。

该实例源代码引用自:https://github.com/yksz/go-design-patterns/blob/master/structure/facade.go

package main

import (
 "fmt"
)

const (
 BOOT_ADDRESS = 0
 BOOT_SECTOR  = 0
 SECTOR_SIZE  = 0
)

type CPU struct{}

func (c *CPU) Freeze() {
 fmt.Println("CPU.Freeze()")
}

func (c *CPU) Jump(position int) {
 fmt.Println("CPU.Jump()")
}

func (c *CPU) Execute() {
 fmt.Println("CPU.Execute()")
}

type Memory struct{}

func (m *Memory) Load(position int, data []byte) {
 fmt.Println("Memory.Load()")
}

type HardDrive struct{}

func (hd *HardDrive) Read(lba int, size int) []byte {
 fmt.Println("HardDrive.Read()")
 return make([]byte, 0)
}

type ComputerFacade struct {
 processor *CPU
 ram       *Memory
 hd        *HardDrive
}

func NewComputerFacade() *ComputerFacade {
 return &ComputerFacade{new(CPU), new(Memory), new(HardDrive)}
}

func (c *ComputerFacade) start() {
 c.processor.Freeze()
 c.ram.Load(BOOT_ADDRESS, c.hd.Read(BOOT_SECTOR, SECTOR_SIZE))
 c.processor.Jump(BOOT_ADDRESS)
 c.processor.Execute()
}

func main() {
 computer := NewComputerFacade()
 computer.start()
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.

本文的完整源码,已经同步收录到我整理的电子教程里啦,可向我的公众号「网管叨bi叨」发送关键字【设计模式】领取。

公众号「网管叨bi叨」发送关键字【设计模式】领取。

使用外观模式的知名库

要说外观模式在实际开发中的应用,首先让我想起来的就是Java 里的Slf4j,它是一个抽象层,让用户对日志的操作统一由Slf4j跟用户去对接,用户用这个抽象层的API来写日志, 底层具体用什么日志工具实现用户完全不用关心,由Slf4j来对接Log4j、LogBack 这些日志工具,这样就可以更方便地移植了。

这个抽象层 Slf4j 就是Simple logging Facade For Java 的简称,从名字里我们也能看出来,它是一个外观模式的实践应用,由于普及度很高,很多讲解外观模式的教程里都会提及它,拿它的实现来给读者做分析。

总结

外观模式的优点

  • 简化了调用过程,不用深入了解子系统,以防给子系统带来风险。
  • 减少系统依赖,松散耦合。
  • 更好地划分访问层次,提高了安全性。
  • 遵循迪米特法则

外观模式的缺点

  • 当增加子系统和扩展子系统行为时,需要对外观进行重复更改,不符合开闭原则,可能容易带来未知风险。