前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >2023 跟我一起学设计模式:外观模式(Facade)

2023 跟我一起学设计模式:外观模式(Facade)

作者头像
用户1418987
发布2023-10-16 09:43:15
3340
发布2023-10-16 09:43:15
举报
文章被收录于专栏:coder

外观模式

亦称: Facade、门面模式

外观模式是一种结构型设计模式, 能为程序库、 框架或其他复杂类提供一个简单的接口。

问题

假设你必须在代码中使用某个复杂的库或框架中的众多对象。 正常情况下, 你需要负责所有对象的初始化工作、 管理其依赖关系并按正确的顺序执行方法等。

最终, 程序中类的业务逻辑将与第三方类的实现细节紧密耦合, 使得理解和维护代码的工作很难进行。

解决方案

外观类为包含许多活动部件的复杂子系统提供一个简单的接口。 与直接调用子系统相比, 外观提供的功能可能比较有限, 但它却包含了客户端真正关心的功能。

如果你的程序需要与包含几十种功能的复杂库整合, 但只需使用其中非常少的功能, 那么使用外观模式会非常方便,

例如, 上传猫咪搞笑短视频到社交媒体网站的应用可能会用到专业的视频转换库, 但它只需使用一个包含 encode­(filename, format)方法 (以文件名与文件格式为参数进行编码的方法) 的类即可。 在创建这个类并将其连接到视频转换库后, 你就拥有了自己的第一个外观。

真实世界类比

电话购物。

当你通过电话给商店下达订单时, 接线员就是该商店的所有服务和部门的外观。 接线员为你提供了一个同购物系统、 支付网关和各种送货服务进行互动的简单语音接口。

外观模式结构

  1. 外观 (Facade) 提供了一种访问特定子系统功能的便捷方式, 其了解如何重定向客户端请求, 知晓如何操作一切活动部件。
  2. 创建附加外观 (Additional Facade) 类可以避免多种不相关的功能污染单一外观, 使其变成又一个复杂结构。 客户端和其他外观都可使用附加外观。
  3. 复杂子系统 (Complex Subsystem) 由数十个不同对象构成。 如果要用这些对象完成有意义的工作, 你必须深入了解子系统的实现细节, 比如按照正确顺序初始化对象和为其提供正确格式的数据。 子系统类不会意识到外观的存在, 它们在系统内运作并且相互之间可直接进行交互。
  4. 客户端 (Client) 使用外观代替对子系统对象的直接调用。

伪代码

在本例中, 外观模式简化了客户端与复杂视频转换框架之间的交互。

使用单个外观类隔离多重依赖的示例

你可以创建一个封装所需功能并隐藏其他代码的外观类, 从而无需使全部代码直接与数十个框架类进行交互。 该结构还能将未来框架升级或更换所造成的影响最小化, 因为你只需修改程序中外观方法的实现即可。

代码语言:javascript
复制
// 这里有复杂第三方视频转换框架中的一些类。我们不知晓其中的代码,因此无法
// 对其进行简化。

class VideoFile
// ……

class OggCompressionCodec
// ……

class MPEG4CompressionCodec
// ……

class CodecFactory
// ……

class BitrateReader
// ……

class AudioMixer
// ……


// 为了将框架的复杂性隐藏在一个简单接口背后,我们创建了一个外观类。它是在
// 功能性和简洁性之间做出的权衡。
class VideoConverter is
    method convert(filename, format):File is
        file = new VideoFile(filename)
        sourceCodec = (new CodecFactory).extract(file)
        if (format == "mp4")
            destinationCodec = new MPEG4CompressionCodec()
        else
            destinationCodec = new OggCompressionCodec()
        buffer = BitrateReader.read(filename, sourceCodec)
        result = BitrateReader.convert(buffer, destinationCodec)
        result = (new AudioMixer()).fix(result)
        return new File(result)

// 应用程序的类并不依赖于复杂框架中成千上万的类。同样,如果你决定更换框架,
// 那只需重写外观类即可。
class Application is
    method main() is
        convertor = new VideoConverter()
        mp4 = convertor.convert("funny-cats-video.ogg", "mp4")
        mp4.save()

外观模式适合应用场景

如果你需要一个指向复杂子系统的直接接口, 且该接口的功能有限, 则可以使用外观模式。

子系统通常会随着时间的推进变得越来越复杂。 即便是应用了设计模式, 通常你也会创建更多的类。 尽管在多种情形中子系统可能是更灵活或易于复用的, 但其所需的配置和样板代码数量将会增长得更快。 为了解决这个问题, 外观将会提供指向子系统中最常用功能的快捷方式, 能够满足客户端的大部分需求。

如果需要将子系统组织为多层结构, 可以使用外观。

创建外观来定义子系统中各层次的入口。 你可以要求子系统仅使用外观来进行交互, 以减少子系统之间的耦合。

让我们回到视频转换框架的例子。 该框架可以拆分为两个层次: 音频相关和视频相关。 你可以为每个层次创建一个外观, 然后要求各层的类必须通过这些外观进行交互。 这种方式看上去与中介者模式非常相似。

实现方式

  1. 考虑能否在现有子系统的基础上提供一个更简单的接口。 如果该接口能让客户端代码独立于众多子系统类, 那么你的方向就是正确的。
  2. 在一个新的外观类中声明并实现该接口。 外观应将客户端代码的调用重定向到子系统中的相应对象处。 如果客户端代码没有对子系统进行初始化, 也没有对其后续生命周期进行管理, 那么外观必须完成此类工作。
  3. 如果要充分发挥这一模式的优势, 你必须确保所有客户端代码仅通过外观来与子系统进行交互。 此后客户端代码将不会受到任何由子系统代码修改而造成的影响, 比如子系统升级后, 你只需修改外观中的代码即可。
  4. 如果外观变得过于臃肿, 你可以考虑将其部分行为抽取为一个新的专用外观类。

外观模式优缺点

  • 你可以让自己的代码独立于复杂子系统。
  • 外观可能成为与程序中所有类都耦合的上帝对象。

代码示例

Go 外观模式讲解和代码示例

外观是一种结构型设计模式, 能为复杂系统、 程序库或框架提供一个简单 (但有限) 的接口。

尽管外观模式降低了程序的整体复杂度, 但它同时也有助于将不需要的依赖移动到同一个位置。

概念示例

人们很容易低估使用信用卡订购披萨时幕后工作的复杂程度。 在整个过程中会有不少的子系统发挥作用。 下面是其中的一部分:

  • 检查账户
  • 检查安全码
  • 借记/贷记余额
  • 账簿录入
  • 发送消息通知

在如此复杂的系统中, 可以说是一步错步步错, 很容易就会引发大的问题。 这就是为什么我们需要外观模式, 让客户端可以使用一个简单的接口来处理众多组件。 客户端只需要输入卡片详情、 安全码、 支付金额以及操作类型即可。

外观模式会与多种组件进一步地进行沟通, 而又不会向客户端暴露其内部的复杂性。

walletFacade.go: 外观
代码语言:javascript
复制
package main

import (
  "fmt"
)

type WalletFacade struct {
  account      *Account
  wallet       *Wallet
  securityCode *SecurityCode
  notification *Notification
  ledger       *Ledger
}

func newWalletFacade(accountID string, code int) *WalletFacade {
  fmt.Println("Starting create account")
  var walletFacade = &WalletFacade{
    account:      newAccount(accountID),
    securityCode: newSecurityCode(code),
    wallet:       newWallet(),
    notification: &Notification{},
    ledger:       &Ledger{},
  }
  fmt.Println("Account created")
  return walletFacade
}

func (w *WalletFacade) addMoneyToWallet(accountID string, securityCode int, amount int) error {
  fmt.Println("Start add money to wallet")
  err := w.account.checkAccount(accountID)
  if err != nil {
    return err
  }
  err = w.securityCode.checkCode(securityCode)
  if err != nil {
    return err
  }

  w.wallet.creditBalance(amount)
  w.notification.sendWalletCreditNotification()
  w.ledger.makeEntry(accountID, "credit", amount)
  return nil
}

func (w *WalletFacade) deductMoneyFromWallet(accountID string, securityCode, amount int) error {
  fmt.Println("Starting debit money from wallet")
  err := w.account.checkAccount(accountID)
  if err != nil {
    return err
  }
  err = w.securityCode.checkCode(securityCode)
  if err != nil {
    return err
  }

  err = w.wallet.debitBalance(amount)
  if err != nil {
    return err
  }
  w.notification.sendWalletCreditNotification()
  w.ledger.makeEntry(accountID, "debit", amount)
  return nil
}
account.go: 复杂子系统的组成部分
代码语言:javascript
复制
package main

import "fmt"

type Account struct {
  name string
}

func newAccount(name string) *Account {
  return &Account{
    name: name,
  }
}

func (a *Account) checkAccount(name string) error {
  if a.name != name {
    return fmt.Errorf("Account Name is incorrect")
  }
  fmt.Println("Account Verified")
  return nil
}
securityCode.go: 复杂子系统的组成部分
代码语言:javascript
复制
package main

import "fmt"

type SecurityCode struct {
  code int
}

func newSecurityCode(code int) *SecurityCode {
  return &SecurityCode{code: code}
}

func (s *SecurityCode) checkCode(incomingCode int) error {
  if s.code != incomingCode {
    return fmt.Errorf("Security Code is incorrect")
  }
  fmt.Println("SecurityCode Verified")
  return nil
}
wallet.go: 复杂子系统的组成部分
代码语言:javascript
复制
package main

import "fmt"

type Wallet struct {
  balance int
}

func newWallet() *Wallet {
  return &Wallet{balance: 0}
}

func (w *Wallet) creditBalance(amount int) {
  w.balance += amount
  fmt.Println("Wallet balance added successfully")
  return
}

func (w *Wallet) debitBalance(amount int) error {
  if w.balance < amount {
    return fmt.Errorf("Balance is not sufficient")
  }
  w.balance -= amount
  return nil
}
ledger.go: 复杂子系统的组成部分
代码语言:javascript
复制
package main

import "fmt"

type Ledger struct{}

func (s *Ledger) makeEntry(accountID, txnType string, amount int) {
  fmt.Printf("Make ledger entry for accountId %s with txnType %s for amount %d\n", accountID, txnType, amount)
  return
}
notification.go: 复杂子系统的组成部分
代码语言:javascript
复制
package main

import "fmt"

type Notification struct{}

func (n *Notification) sendWalletCreditNotification() {
  fmt.Println("sending wallet credit notification")
}

func (n *Notification) sendWalletDebitNotification() {
  fmt.Println("Sending wallet debit notification")
}
main.go: 客户端代码
代码语言:javascript
复制
package main

import (
  "fmt"
  "log"
)

func main() {
  fmt.Println()
  walletFacade := newWalletFacade("abc", 1234)
  fmt.Println()
  err := walletFacade.addMoneyToWallet("abc", 1234, 10)
  if err != nil {
    log.Fatalf("error: %s \n", err.Error())
  }
  fmt.Println()
  err = walletFacade.deductMoneyFromWallet("abc", 1234, 5)
  if err != nil {
    log.Fatalf("error: %s \n", err.Error())
  }
}
output.txt: 执行结果

相关设计模式

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-06-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 外观模式
    • 问题
      • 解决方案
        • 真实世界类比
          • 外观模式结构
            • 伪代码
              • 外观模式适合应用场景
                • 实现方式
                  • 外观模式优缺点
                    • 代码示例
                      • walletFacade.go: 外观
                      • account.go: 复杂子系统的组成部分
                      • securityCode.go: 复杂子系统的组成部分
                      • wallet.go: 复杂子系统的组成部分
                      • ledger.go: 复杂子系统的组成部分
                      • notification.go: 复杂子系统的组成部分
                      • main.go: 客户端代码
                      • output.txt: 执行结果
                  • Go 外观模式讲解和代码示例
                  • 概念示例
                    • 相关设计模式
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档