7、外观模式

外观模式

文章目录

  • 外观模式
    • 概述
    • 结构
    • 结构实现
    • 练习
    • 源代码
    • 抽象外观类
      • 案例
    • 外观模式优/缺点与适用环境
      • 优点
      • 缺点
      • 适用环境

概述

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

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

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

结构

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
如有不对的地方,欢迎在评论区指正
欢迎大家关注我,我将持续更新更多的文章


热门文章

暂无图片
编程学习 ·

Python/PTA--第2章 分段计算居民水费 (10分)

第2章-13 分段计算居民水费 (10分) 为鼓励居民节约用水&#xff0c;自来水公司采取按用水量阶梯式计价的办法&#xff0c;居民应交水费y&#xff08;元&#xff09;与月用水量x&#xff08;吨&#xff09;相关&#xff1a;当x不超过15吨时&#xff0c;y4x/3&#xff1b;超过后…
暂无图片
编程学习 ·

那些貌似真诚的人

是不是经常遇到这种人&#xff0c;大家的评价都是他很真诚&#xff0c;人也很简单&#xff0c;事情好&#xff0c;事情往往会有利于他&#xff0c;而且他过的很快乐。 残酷的现实是没有简单的快乐&#xff0c;只要是丰富的快乐&#xff0c;他就不会简单。一个人貌似真诚不见得…
暂无图片
编程学习 ·

JetBrains软件怎么设置中文?值得一看!

JetBrains是一家捷克的软件开发公司&#xff0c;此公司最为人所熟知的产品是Java编程语言开发撰写时所用的集成开发环境&#xff1a;IntelliJ IDEA。jetbrains全家桶基本都是英文的&#xff0c;有的朋友使用起来很不方便&#xff0c;那么jetbrains全家桶怎么汉化呢&#xff1f;…
暂无图片
编程学习 ·

ElasticSearch应用篇-搜索增强

ES有一些非常强大的能力&#xff0c;例如&#xff1a;根据用户搜索的时候&#xff0c;也可以搜索同义词&#xff0c;也可以基于语义进行分词&#xff0c;返回最最适合的结果&#xff0c;ElasticSearch是如何实现这种能力的呢&#xff1f; 一、ES搜索与Analyzer 1.ES搜索过程 …
暂无图片
编程学习 ·

框架的诞生-零:为什么写框架?

框架的诞生-零&#xff1a;为什么写框架&#xff1f;题外话什么是框架&#xff1f;框架解决什么问题&#xff1f;为什么写框架&#xff1f;为了造一个更好更适合的轮子为了学习和实践总结一些游戏客户端框架参考心里话框架开发系列文章最后题外话 大家好&#xff0c;很高兴&am…
暂无图片
编程学习 ·

动态规划之股票问题123

问题描述&#xff1a; 给定一个数组&#xff0c;它的第 i 个元素是一支给定的股票在第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。 注意: 你不能同时参与多笔交易&#xff08;你必须在再次购买前出售掉之前的股票&#xff09;。 来源&a…
暂无图片
编程学习 ·

北邮CSAPP第三章之数据格式与程序编码

程序的机器级表示 本章学习内容&#xff1a;汇编代码 高级语言屏蔽了程序的机器级实现。 用高级语言编写的程序可以在不同的机器上运行&#xff0c;汇编代码则于特定机器密切相关 学习汇编代码能理解编译器优化能力&#xff0c;并分析代码中隐含的低效率 此外&#xff0c;高…
暂无图片
编程学习 ·

安装gradle

安装gradle1、下载二进制包解压2、配置环境变量3、验证&#xff0c;如下成功4、配置Gradle仓库源5、idea配置1、下载二进制包解压 2、配置环境变量 3、验证&#xff0c;如下成功 4、配置Gradle仓库源 在Gradle安装目录下的 init.d 文件夹下&#xff0c;新建一个 init.gradle …
暂无图片
编程学习 ·

CodeGen标记循环

CodeGen标记循环 标记循环是一个模板文件构造&#xff0c;它允许您迭代CodeGen拥有的标记信息的集合。为了使用标记循环&#xff0c;必须基于至少定义了一个字段标记的存储库结构生成代码。 标记循环由一对匹配的<Tag_LOOP>和</Tag_LOOP>标记分隔&#xff0c;它们围…
暂无图片
编程学习 ·

CodeGen按钮循环

CodeGen按钮循环 按钮循环是一个模板文件构造&#xff0c;它允许您迭代CodeGen拥有的按钮信息集合。 在按钮循环中处理的按钮的定义可以来自两个位置之一。 如果基于UI工具箱输入窗口定义进行处理&#xff0c;则默认情况下&#xff0c;按钮集合由该输入窗口定义中的按钮确定。否…
暂无图片
编程学习 ·

CodeGen准备存储库

CodeGen准备存储库 CodeGen几乎总是与提供用于生成源文件的元数据的存储库结构一起使用&#xff0c;并且许多令牌需要使用存储库结构。 基本要求是有一个结构定义&#xff0c;并且该结构定义包含一个或多个字段定义。有些标记还要求定义键&#xff0c;有些则需要具有结构赋值的…
暂无图片
编程学习 ·

2020年PMP笔记归纳第六章项目进度管理

学习目标&#xff1a; 掌握第六章项目进度管理 学习内容&#xff1a; 内容章节 6.1 规划进度管理 6.2 定义活动 6.3 排列活动顺序 6.4 估算活动持续时间 6.5 制定进度计划 6.6 控制进度 第六章PMBOK概述中的重点内容 具有未完成项的迭代型进度计划。基于适应型生命周期的滚动…
暂无图片
编程学习 ·

无人机项目跟踪记录--失败

查看了源代码&#xff0c;发现无法往下进行了&#xff0c;原因如下&#xff1a; 1、这个第二代机移植了cleanfly开源的源码程序&#xff0c;似乎这个开发板的目的是为了学习cleanfly源码的&#xff0c;而不是为了初步了解无人机的&#xff0c;已经是个很高阶段了&#xff0c;超…
暂无图片
编程学习 ·

Python3正则表达式之:(?(id/name)yes-pattern|no-

Python3正则表达式之&#xff1a;(?(id/name)yes-pattern|no-pattern)条件性匹配 1. 用途 (?(id/name)yes-pattern|no-pattern)的作用是&#xff1a; 对于给出的id或者name&#xff0c;先尝试去匹配 yes-pattern部分的内容&#xff1b; 如果id或name条件不满足&#xff0…
暂无图片
编程学习 ·

socket简单使用(Android、c、QT不同场景下使用)

一&#xff1a;Socket使用场景&#xff1a; socket做网络通信使用&#xff0c;例如游戏中的聊天&#xff0c;IM聊天&#xff08;QQ微信等社交&#xff09;&#xff0c;这些是大型的场景&#xff1b;还有一些是次一等的场景&#xff0c;例如一套本地使用的软件&#xff0c;需要…
暂无图片
编程学习 ·

网络信号与数制转换

文章目录信号与传输介质信号双绞线双绞线的连接规范光纤概述光纤的分类光纤接口无线传输介质无线电波微波天线计算机的数质信号与传输介质 信号 什么是信号&#xff1a;信息&#xff0c;数据&#xff0c;信号 信号的分类&#xff1a;①模拟信号②数字信号 模拟与数字的区别&a…
暂无图片
编程学习 ·

樊昌信版通信原理期末复习第一章绪论

第1章 绪论 一、知识点梳理 1、通信的目的&#xff1a;传递消息中所包含的信息。 2、消息&#xff1a;是物质或精神状态的一种反映。 3、信息&#xff1a;是消息中包含的有效内容。 4、通信系统的一般模型 信源输入变换器&#xff1a;将非电物理量变成电信号。 发送设备&…