【设计模式】结构型模式其五: 外观模式

841 阅读5分钟

外观模式

什么是外观模式

外观模式是一种软件设计模式,它提供了一个简单的接口,隐藏了一个子系统的复杂性,使得客户端可以更轻松地使用该子系统。外观模式通过将客户端与子系统之间的交互封装在一个高层接口中,简化了客户端的代码,并提高了系统的可维护性和可扩展性

一句话,外观模式是简化了客户端操作

image.png

比如: 我们出门点餐,只需要告诉服务员我们要吃什么,而不用管菜该怎么做,服务员就是简化了我们的操作。

为复杂子系统提供一个简单的访问入口。

分析:

  • 一个客户类需要和多个业务类交互,而这些需要交互的业务类经常会作为一个整体出现
  • 引入一个新的外观类(Facade)来负责和多个业务类【子系统(Subsystem)】进行交互,而客户类只需与外观类交互
  • 为多个业务类的调用提供了一个统一的入口简化了类与类之间的交互

image.png

外观类的作用

分析

  • 没有外观类:每个客户类需要和多个子系统之间进行复杂的交互,系统的耦合度很大
  • 引入外观类:客户类只需要直接与外观类交互,客户类与子系统之间原有的复杂引用关系由外观类来实现,从而降低了系统的耦合度

定义

外观模式的定义:

  • 又称为门面模式
  • 是迪米特法则的一种具体实现
  • 通过引入一个新的外观角色来降低原有系统的复杂度,同时降低客户类与子系统之间的耦合度
  • 所指的子系统是一个广义的概念,它可以是一个类、一个功能模块、系统的一个组成部分或者一个完整的系统

模式结构

image.png

外观模式包含以下2个角色:

  1. Facade(外观角色)
  2. SubSystem(子系统角色)

实例理解

某软件公司要开发一个可应用于多个软件的文件加密模块,该模块可以对文件中的数据进行加密并将加密之后的数据存储在一个新文件中,具体的流程包括3个部分,分别是读取源文件、加密、保存加密之后的文件,其中,读取文件和保存文件使用流来实现,加密操作通过求模运算实现。这3个操作相对独立,为了实现代码的独立重用,让设计更符合单一职责原则,这3个操作的业务代码封装在3个不同的类中。 现使用外观模式设计该文件加密模块。

抽象外观类

public abstract class AbstractEncryptFacade {
   public abstract void fileEncrypt(String fileNameSrc, String fileNameDes);  
}

子系统类

不同的子系统执行不同的功能

// 读取文件

//文件读取类:子系统类
public class FileReader {
   public String read(String fileNameSrc) {
      System.out.print("读取文件,获取明文:");
      StringBuffer sb = new StringBuffer();
      try{
         FileInputStream inFS = new FileInputStream(fileNameSrc);      
         int data;
      while((data = inFS.read())!= -1) {
         sb = sb.append((char)data);
      }
          inFS.close();
          System.out.println(sb.toString());
      }
      catch(FileNotFoundException e) {
         System.out.println("文件不存在!");
      }
      catch(IOException e) {
         System.out.println("文件操作错误!");
      }
      return sb.toString();
   }
}

// 数据加密

public class NewCipherMachine {
   public String encrypt(String plainText) {
      System.out.print("数据加密,将明文转换为密文:");
      String es = "";
      int key = 10;//设置密钥,移位数为10
      for (int i = 0; i < plainText.length(); i++) {
         char c = plainText.charAt(i);
         //小写字母移位
         if (c >= 'a' && c <= 'z') {
            c += key % 26;
             if (c > 'z') c -= 26;
             if (c < 'a') c += 26;
         }
            //大写字母移位
         if (c >= 'A' && c <= 'Z') {
            c += key % 26;
             if (c > 'Z') c -= 26;
             if (c < 'A') c += 26;
         }
         es += c;
      }
      System.out.println(es);
      return es;
   }
}

// 保存文件

//文件保存类:子系统类
public class FileWriter {
   public void write(String encryptStr,String fileNameDes) {
      System.out.println("保存密文,写入文件。");
      try{
          FileOutputStream outFS = new FileOutputStream(fileNameDes);
          outFS.write(encryptStr.getBytes());
          outFS.close();
      }  
      catch(FileNotFoundException e) {
         System.out.println("文件不存在!");
      }
      catch(IOException e) {
         System.out.println("文件操作错误!");
      }     
   }
}

具体外观类(旧的)

//加密外观类:外观类
public class EncryptFacade {
   //维持对其他对象的引用
   private FileReader reader;
   private CipherMachine cipher;
   private FileWriter writer;
   
   public EncryptFacade() {
      reader = new FileReader();
      cipher = new CipherMachine();
      writer = new FileWriter();
   }
   
   //调用其他对象的业务方法
   public void fileEncrypt(String fileNameSrc, String fileNameDes) {
      String plainStr = reader.read(fileNameSrc);
      String encryptStr = cipher.encrypt(plainStr);
      writer.write(encryptStr,fileNameDes);
   }
}

具体外观类(新的)

public class NewEncryptFacade extends AbstractEncryptFacade {
   private FileReader reader;
   private NewCipherMachine cipher;
   private FileWriter writer;
   
   // 注入三个子系统类
   public NewEncryptFacade() {
      reader = new FileReader();
      cipher = new NewCipherMachine();
      writer = new FileWriter();
   }
   
   // 调用该方法去执行三个子系统的方法
   public void fileEncrypt(String fileNameSrc, String fileNameDes) {
      String plainStr = reader.read(fileNameSrc);
      String encryptStr = cipher.encrypt(plainStr);
      writer.write(encryptStr,fileNameDes);
   }
}

为了能通过配置来切换外观类,引入配置文件

XML文件类

<?xml version="1.0"?>
<config>
    <className>designpatterns.facade.NewEncryptFacade</className>
</config>

XML文件读取类

public class XMLUtil {
   //该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
   public static Object getBean() {
      try {
         //创建DOM文档对象
         DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
         DocumentBuilder builder = dFactory.newDocumentBuilder();
         Document doc;
         doc = builder.parse(new File("./config.xml"));

         //获取包含类名的文本节点
         NodeList nl = doc.getElementsByTagName("className");
         Node classNode=nl.item(0).getFirstChild();
         String cName=classNode.getNodeValue();

         //通过类名生成实例对象并将其返回
         Class c=Class.forName(cName);
         Object obj=c.getConstructor().newInstance();
         return obj;
      }
      catch(Exception e) {
         e.printStackTrace();
         return null;
      }
   }
}

客户端调用类

客户端通过使用XML文件读取配置生成具体的外观类

public class Client {
   public static void main(String args[]) {
      AbstractEncryptFacade ef;
      ef = (AbstractEncryptFacade)XMLUtil.getBean();
      ef.fileEncrypt("./src.txt","./des.txt");
   }
}

输出:

读取文件,获取明文:Hello world!
数据加密,将明文转换为密文:Rovvy gybvn!
保存密文,写入文件。

类图

image.png

与单例模式结合

如果该外观类属于长期不变的,可以将其设计为单例模式。

image.png

具体操作:

外观类里维持一个自己的对象, 私有化构造函数

模式优缺点

模式优点:

  • 对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易
  • 实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可
  • 子系统的内部变化不会影响到外观对象,一个子系统的修改对其他子系统也没有任何影响

模式缺点

  • 不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活性
  • 如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则