7、外观模式

  • Post author:
  • Post category:其他




外观模式



概述

外观模式:为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

外观模式又称为门面模式,是一种对象结构型模式。通过引入一个新的外观角色可以降低原有系统的复杂度,同时降低客户类与子系统的耦合度。在外观模式中,一个子系统的外部与其内部的通信通过一个统一的外观类进行,外观类将客户类与子系统的内部复杂性分隔开,使得客户类只需要与外观角色打交道,而不需要与子系统内部的很多对象打交道。

类似于,自己泡茶和茶馆喝茶的区别。自己泡茶需要自行准备茶叶、茶具和开水,而茶馆喝茶只需要跟服务员交谈,泡茶由服务员完成。此时服务员充当外观模式的外观类。



结构

image-20201218142419372

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

  1. Facade(外观角色):在客户端可以调用它的方法,在外观角色中可以知道相关的(一个或者多个)子系统的功能和责任;在正常情况下,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理。
  2. SubSystem(子系统角色):在软件系统中可以有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能;每一个子系统都可以被客户端直接调用,或者被外观角色调用,它处理由外观类传过来的请求;子系统并不知道外观的存在,对于子系统而言,外观角色仅仅是另外一个客户端而已。



结构实现

子系统类:通常是一些业务类,实现了一些具体的、独立的业务功能。

public class SubSystemA{
    public void methodA(){
        //业务实现代码
    }
}

public class SubSystemB{
    public void methodB(){
        //业务实现代码
    }
}

public class SubSystemC{
    public void methodC(){
        //业务实现代码
    }
}

外观类:

public class Facade{
    private SubSystemA obj1 = new SubSystemA();
    private SubSystemB obj2 = new SubSystemB();
    private SubSystemC obj3 = new SubSystemC();
    
    public void method(){
        obj1.methodA();
        obj2.methodB();
        obj3.methodC();
    }
}

客户端类:

public class Client{
    public static void main(String args[]){
        Facade facades = new Facade();
        facade.method();
    }
}



练习

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

现使用外观模式设计该文件加密模块。

image-20201218143345387



源代码

FileReader.java:文件读取类,充当子系统类

package facade;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

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();
   }
}

CipherMachine.java:数据加密类,充当子系统类

package facade;

public class CipherMachine {
   public String encrypt(String plainText) {
      System.out.print("数据加密,将明文转换为密文:");
      String es = "";
      for (int i = 0; i < plainText.length(); i++) {
         String c = String.valueOf(plainText.charAt(i)%7);
         es+=c;
      }
      System.out.println(es);
      return es;
   }
}

FileWriter.java:文件保存类,充当子系统类

package facade;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

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("文件操作错误!");
      }
   }
}

EncryptFacade.java:加密外观类,充当外观类

package facade;

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);
   }
}

Client.java:客户端测试类

package facade;

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

运行结果:

img



抽象外观类

在标准的外观模式结构图中,如果需要增加、删除或更换与外观类交互的子系统类,必须修改外观类或客户端的源代码,这将违背开闭原则,因此可以通过引入抽象外观类来对系统进行改进,在一定程度上可以解决该问题。在引入抽象外观类之后,客户端可以针对抽象外观类进行编程,对于新的业务需求,不需要修改原有外观类,而对应增加一个新的具体外观类,由新的具体外观类来关联新的子系统对象,同时通过修改配置文件来达到不修改任何源代码并更换外观类的目的。



案例

img

按照上面的练习进行扩展:

更换一个加密类,不再使用原有的基于求模运算的加密类CipherMachine,而改为基于移位运算的新加密类NewCipherMachine。

NewCipherMachine.java:新加密类

package facade;

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;
   }
}

客户端类针对抽象外观类AbstractEncryptFacade进行编程:

package facade;

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

新增具体加密外观类

package facade;

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);
   }
}

config.xml:配置文件存储具体外观类的类名

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

XMLUtil.java:工具类

package facade;

import javax.xml.parsers.*;
import org.w3c.dom.*;
import java.io.*;

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("src//facade//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.newInstance();
            return obj;
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
            return null;
        }
    }
}

客户端类:

package facade;

public class Client2 {
   public static void main(String[] args) {
      AbstractEncryptFacade ef; //针对抽象外观类编程
      //读取配置文件,反射生成对象
      ef = (AbstractEncryptFacade)XMLUtil.getBean();
      ef.fileEncrypt("src//facade//src.txt", "src//facade//des.txt");
   }
}

运行结果:

img

原有外观类EncryptFacade也需作为抽象外观类AbstractEncryptFacade的子类,在更换具体外观类时只需修改配置文件,无须修改源代码,符合开闭原则。



外观模式优/缺点与适用环境



优点

  1. 它对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易。通过引入外观模式,客户端代码将变得很简单,与之关联的对象也很少。
  2. 它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可。
  3. 一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。



缺点

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



适用环境

  1. 当要为访问一系列复杂的子系统提供一个简单入口时可以使用外观模式。
  2. 客户端程序与多个子系统之间存在很大的依赖性。引入外观类可以将子系统与客户端解耦,从而提高子系统的独立性和可移植性。
  3. 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。

本篇文章参考书籍有:

《Java设计模式》 刘伟——清华大学出版社,2018



作者:

阿涛




CSDN博客主页:

https://blog.csdn.net/qq_43313113




如有不对的地方,欢迎在评论区指正



欢迎大家关注我,我将持续更新更多的文章




版权声明:本文为qq_43313113原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。