外观模式(结构型模式)
模式概述
外观模式有的地方又叫门面模式,是一种结构型设计模式,为程序库、 框架或其他功能复杂的类提供一个统一的、简单的访问接口。
问题
大多数程序员都有过开发复杂应用的经历,不仅需要负责所有对象的初始化,还得管理其依赖关系并按正确的顺序执行方法等。
而代码中的业务逻辑又常与第三方类的实现细节紧密耦合,使得理解和维护代码的工作很难推进。
方案
通过一个简单的接口,来包含众多复杂子系统的各个功能。这与直接调用众多的子系统相比,既简化了调用过程中的繁杂操作(例如初始化、依赖管理及执行顺序),又将客户端真正关心的功能给呈现出来。

结构

实现
Java
/**
* 视频文件
*
*/
public class VideoFile {
private String name;
private String codecType;
public VideoFile(String name) {
this.name = name;
this.codecType = name.substring(name.indexOf(".") + 1);
}
public String getCodecType() {
return codecType;
}
}
/**
* 编码接口
*
*/
public interface Codec {
}
/**
* MP4编码
*
*/
public class MPEG4Codec implements Codec {
public String type = "mp4";
}
/**
* OGG编码
*
*/
public class OggCodec implements Codec {
public String type = "ogg";
}
/**
* 编码工厂
*
*/
public class CodecFactory {
public static Codec extract(VideoFile file) {
String type = file.getCodecType();
if (type.equals("mp4")) {
System.out.println("正在转换mp4为音频...");
return new MPEG4Codec();
} else {
System.out.println("正在转换ogg为音频...");
return new OggCodec();
}
}
}
/**
* 读取器
*
*/
public class BitrateReader {
public static VideoFile read(VideoFile file, Codec codec) {
System.out.println("正在读取视频文件...");
return file;
}
public static VideoFile convert(VideoFile buffer, Codec codec) {
System.out.println("正在写入视频文件...");
return buffer;
}
}
/**
* 音频修复
*
*/
public class AudioMixer {
public File fix(VideoFile result){
System.out.println("正在修复音频...");
return new File("tmp");
}
}
/**
* 门面
*
*/
public class VideoConversionFacade {
public File convertVideo(String fileName, String format) {
System.out.println("视频转换开始...");
VideoFile file = new VideoFile(fileName);
Codec sourceCodec = CodecFactory.extract(file);
Codec codec;
if (format.equals("mp4")) {
codec = new MPEG4Codec();
} else {
codec = new OggCodec();
}
VideoFile buffer = BitrateReader.read(file, sourceCodec);
VideoFile intermediateResult = BitrateReader.convert(buffer, codec);
File result = (new AudioMixer()).fix(intermediateResult);
System.out.println("视频转换完成...");
return result;
}
}
/**
* 客户端
*
*/
public class Client {
public static void main(String[] args) {
VideoConversionFacade converter = new VideoConversionFacade();
File mp4Video = converter.convertVideo("test.ogg", "mp4");
}
}
Go
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")
walletFacacde := &WalletFacade{
account: newAccount(accountID),
securityCode: newSecurityCode(code),
wallet: newWallet(),
notification: &Notification{},
ledger: &Ledger{},
}
fmt.Println("Account created")
return walletFacacde
}
func (w *WalletFacade) addMoneyToWallet(accountID string, securityCode int, amount int) error {
fmt.Println("Starting 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 int, 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.sendWalletDebitNotification()
w.ledger.makeEntry(accountID, "debit", amount)
return nil
}
/**
* 账户
*
*/
type Account struct {
name string
}
func newAccount(accountName string) *Account {
return &Account{
name: accountName,
}
}
func (a *Account) checkAccount(accountName string) error {
if a.name != accountName {
return fmt.Errorf("账户名不正确")
}
fmt.Println("账户被核实")
return nil
}
/**
* 安全码
*
*/
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("安全码不正确")
}
fmt.Println("安全码被核实")
return nil
}
/**
* 钱包
*
*/
type Wallet struct {
balance int
}
func newWallet() *Wallet {
return &Wallet{
balance: 0,
}
}
func (w *Wallet) creditBalance(amount int) {
w.balance += amount
fmt.Println("钱包余额增加成功")
return
}
func (w *Wallet) debitBalance(amount int) error {
if w.balance < amount {
return fmt.Errorf("余额不足")
}
fmt.Println("余额不足")
w.balance = w.balance - amount
return nil
}
/**
* 分类账户
*
*/
type Ledger struct {
}
func (s *Ledger) makeEntry(accountID, txnType string, amount int) {
fmt.Printf("为税务 %s 类型账户 %s 充值 %d 元\n", txnType, accountID, amount)
return
}
/**
* 通知
*
*/
type Notification struct {
}
func (n *Notification) sendWalletCreditNotification() {
fmt.Println("发送钱包信用通知")
}
func (n *Notification) sendWalletDebitNotification() {
fmt.Println("发送钱包借记通知")
}
/**
* 客户端
*
*/
func main() {
fmt.Println()
walletFacade := newWalletFacade("abc", 1234)
fmt.Println()
err := walletFacade.addMoneyToWallet("abc", 1234, 10)
if err != nil {
fmt.Errorf("Error: %s\n", err.Error())
}
fmt.Println()
err = walletFacade.deductMoneyFromWallet("abc", 1234, 5)
if err != nil {
fmt.Errorf("Error: %s\n", err.Error())
}
}
适用场景
如果需要直接操作复杂子系统的接口,且该接口的功能有限,则可以使用外观模式。
当客户端与多个子系统之间存在很大的依赖性时,可引入外观模式将客户端与子系统解耦。
在多层次结构中,可通过外观模式定义系统中每一层的入口,降低层之间的耦合度。
优缺点
外观模式的优点。
为客户端屏蔽了子系统的复杂性,实现了子系统与客户端之间的解耦。
降低了大型软件系统中的编译依赖性,并简化了系统在不同平台之间的移植过程。
外观模式的缺点。
- 外观类可能成为与程序中所有类都耦合的上帝对象。
相关性
当需要对客户端隐藏子系统创建对象的方式时,可以使用
抽象工厂模式
来代替外观模式
。外观模式
为现有对象定义一个新接口,通常会作用于整个对象子系统上;适配器模式
则会封装已有的接口,且仅封装单个对象。代理模式
与外观模式
的相似之处在于都封装了另一个实体并对其进行初始化,但代理模式
可与服务对象互换,而外观模式
不行。享元模式
展示了如何生成大量的小型对象,而外观模式
则展示了如何用一个对象来代表整个子系统。外观模式
和中介者模式
的职责类似,它们都尝试在大量紧密耦合的类中组织起合作。外观模式
通常可以转换为单例模式
,因为大部分情况下单个外观类就足够了。当只需要对客户端隐藏子系统的实现细节时,可以用
抽象工厂模式
来代替外观模式
。
感谢支持
更多内容,请移步《超级个体》。