设计模式 - 外观模式

实例

手机一键备份功能

假设一个智能手机一键备份数据的应用场景,该功能可以将手机中的通讯录,短信,照片,歌曲等资料一次性备份,在实现过程中需要与多个已有的子系统进行交互,如通讯录子系统、短信子系统等

乍一看该应用场景,如果再添一句需充分考虑系统的灵活性和可扩展性那就与工厂方法模式很类似了,但这里不考虑应用的扩展,只考虑应用的交互


常规的解决方案

  • AddressBookService.java
/**
 * @Description 通讯录子系统
 */
public class AddressBookService {

    public void backUp() {
        System.out.println("通讯录备份成功");
    }

}
  • LocalMusicService.java
/**
 * @Description 本地音乐子系统
 */
public class LocalMusicService {

    public void backUp() {
        System.out.println("本地音乐备份成功");
    }

}
  • PhotographService.java
/**
 * @Description 照片子系统
 */
public class PhotographService {

    public void backUp() {
        System.out.println("照片备份成功");
    }

}
  • ShortMsgService.java
/**
 * @Description 短信子系统
 */
public class ShortMsgService {

    public void backUp() {
        System.out.println("短信备份成功");
    }

}
  • Client.java
/**
 * @Description 客户端
 */
public class Client {
    public static void main(String[] args) {

        AddressBookService addressBookService = new AddressBookService();
        LocalMusicService localMusicService = new LocalMusicService();
        PhotographService photographService = new PhotographService();
        ShortMsgService shortMsgService = new ShortMsgService();

        addressBookService.backUp();
        localMusicService.backUp();
        photographService.backUp();
        shortMsgService.backUp();
    }
}
  • 输出如下:
通讯录备份成功
本地音乐备份成功
照片备份成功
短信备份成功
  • 类图如下:

在这里插入图片描述

  • 该方案存在以下问题,(1)客户端需要与每个子系统进行交互,导致客户端代码较为复杂,且每次使用代码重复;(2)如需更换或扩展备份子系统,每个调用的地方都需要修改,灵活性和扩展性较差
  • 软件系统中经常会有一个类需要和多个其他业务类交互,由于涉及的类较多,导致使用时代码较为复杂,此时就需要一个类似服务员一样的角色来负责和多个业务类的交互

外观模式

概念

  • 外观模式(Facade Pattern):外部与一个子系统的通信通过一个统一的外观角色进行,为子系统中的一组接口提供一个一致的入口
  • 外观模式定义了一个高层接口,让子系统更容易使用
  • 外观模式又称为门面模式,它是一种对象结构型模式

外观模式解决方案

  • BackUpFacade.java
/**
 * @Description 备份外观类
 */
public class BackUpFacade {

    private AddressBookService addressBookService;
    private LocalMusicService localMusicService;
    private PhotographService photographService;
    private ShortMsgService shortMsgService;

    public BackUpFacade() {
        addressBookService = new AddressBookService();
        localMusicService = new LocalMusicService();
        photographService = new PhotographService();
        shortMsgService = new ShortMsgService();
    }

    /**
     * 统一备份
     */
    public void unifiedBackUp() {
        addressBookService.backUp();
        localMusicService.backUp();
        photographService.backUp();
        shortMsgService.backUp();
    }

}
  • Test.java
/**
 * @Description 外观模式测试类
 */
public class Test {
    public static void main(String[] args) {
        BackUpFacade backUpFacade = new BackUpFacade();
        backUpFacade.unifiedBackUp();
    }
}
  • 输出如下:
通讯录备份成功
本地音乐备份成功
照片备份成功
短信备份成功
  • 类图如下:

在这里插入图片描述

  • 外观模式通过引入一个新的外观类来充当软件系统中的"服务员",为多个业务类的调用提供一个统一的入口,简化类之间的调用
  • 外观模式松散了客户端与子系统的耦合关系,客户端不与子系统直接交流,客户端和外观对象进行交流,让子系统内部的模块更容易扩展和维护

方案的演进(配置文件)

  • 由于以上子系统的行为具有一致性,还可通过配置文件 + 反射的方式实现在不修改代码的基础上更换和扩展对一键备份功能的支持
  • config.properties
facadePattern.classNames=AddressBookManager,LocalMusicManager,PhotographManager,ShortMsgManager
  • PropertiesUtil.java
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description Properties工具类
 */
public class PropertiesUtil {

    /**
     * 根据key读取value
     * @Description: 相对路径, properties文件需在classpath目录下,
     *               比如:config.properties在包com.coisini.util,
     *               路径就是/com/coisini/util/config.properties
     * @param filePath
     * @param keyWord
     * @return String
     * @throws
     */
     private static String getProperties(String filePath, String keyWord){
        Properties prop = new Properties();
        String value = null;
        try {
            InputStream inputStream = PropertiesUtil.class.getResourceAsStream(filePath);
            prop.load(inputStream);
            value = prop.getProperty(keyWord);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return value;
     }

    /**
     * 根据配置文件提取类名返回实例对象集合
     * @param filePath
     * @param keyWord
     * @param packagePath
     * @return
     */
    private static List<Object> getBeans(String filePath, String keyWord, String packagePath) {
        String[] classNames = getProperties(filePath, keyWord).split(",");
        return Arrays.stream(classNames).map(className -> {
            try {
                return Class.forName(packagePath + className).newInstance();
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }).filter(Objects::nonNull).collect(Collectors.toList());
    }

    /**
     * 获取外观模式实例对象
     * @return
     */
    public static List<Object> getFacadePatternBeans() {
        return getBeans("/com/coisini/design/util/config.properties",
                "facadePattern.classNames",
                "com.coisini.design.pattern.structural.facade.own.v2.");
    }

}
  • BackUpFacade.java
import java.util.List;
import java.util.stream.Collectors;

/**
 * @Description 备份外观类
 */
public class BackUpFacade {

    private List<AbstractService> abstractServiceList;

    public BackUpFacade() {
        abstractServiceList = PropertiesUtil.getFacadePatternBeans()
                                            .stream()
                                            .map(bean -> (AbstractService) bean)
                                            .collect(Collectors.toList());
    }

    /**
     * 统一备份
     */
    public void unifiedBackUp() {
        this.abstractServiceList.forEach(AbstractService::backUp);
    }

}
  • Test.java
/**
 * @Description 外观模式测试类(配置文件反射实现方式)
 */
public class Test {
    public static void main(String[] args) {
        BackUpFacade backUpFacade = new BackUpFacade();
        backUpFacade.unifiedBackUp();
    }
}
  • 输出如下:
通讯录备份成功
本地音乐备份成功
照片备份成功
短信备份成功

抽象外观类

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

  • AbstractFacade .java

/**
 * @Description 抽象备份外观类
 */
public abstract class AbstractFacade {

    public abstract void unifiedBackUp();

}
  • LocalBackUpFacade.java
/**
 * @Description 本地备份外观类
 */
public class LocalBackUpFacade extends AbstractFacade {

    private LocalMusicService localMusicService;
    private LocalPhotoService localPhotoService;
    private AddressBookService addressBookService;

    public LocalBackUpFacade() {
        localMusicService = new LocalMusicService();
        localPhotoService = new LocalPhotoService();
        addressBookService = new AddressBookService();
    }

    @Override
    public void unifiedBackUp() {
        localMusicService.backUp();
        localPhotoService.backUp();
        addressBookService.backUp();
    }
}
  • OnlineBackUpFacade.java
/**
 * @Description 在线备份外观类
 */
public class OnlineBackUpFacade extends AbstractFacade {

    private OnlineMusicService onlineMusicService;
    private OnlinePhotoService onlinePhotoService;
    private AddressBookService addressBookService;

    public OnlineBackUpFacade() {
        onlineMusicService = new OnlineMusicService();
        onlinePhotoService = new OnlinePhotoService();
        addressBookService = new AddressBookService();
    }

    @Override
    public void unifiedBackUp() {
        onlineMusicService.backUp();
        onlinePhotoService.backUp();
        addressBookService.backUp();
    }
}
  • config.properties
facadePattern.className=OnlineBackUpFacade
  • PropertiesUtil.java
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description Properties工具类
 * @author coisini
 * @date Feb 28, 2022
 * @version 1.0
 */
public class PropertiesUtil {
/**
     * 根据key读取value
     * @Description: 相对路径, properties文件需在classpath目录下,
     *               比如:config.properties在包com.coisini.util,
     *               路径就是/com/coisini/util/config.properties
     * @param filePath
     * @param keyWord
     * @return String
     * @throws
     */
     private static String getProperties(String filePath, String keyWord){
        Properties prop = new Properties();
        String value = null;
        try {
            InputStream inputStream = PropertiesUtil.class.getResourceAsStream(filePath);
            prop.load(inputStream);
            value = prop.getProperty(keyWord);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return value;
     }

    /**
     * 根据配置文件提取类名返回实例对象
     * @param filePath
     * @param keyWord
     * @param packagePath
     * @return
     */
     private static Object getBean(String filePath, String keyWord, String packagePath) {
         try {
             String className = getProperties(filePath, keyWord);
             Class<?> c = Class.forName(packagePath + className);
             return c.newInstance();
         } catch (Exception e) {
             e.printStackTrace();
             return null;
         }

     }
     /**
     * 获取外观模式实例对象
     * @return
     */
    public static Object getFacadePatternBean() {
        return getBean("/com/coisini/design/util/config.properties",
                "facadePattern.className",
                "com.coisini.design.pattern.structural.facade.own.v3.");
    }
}
  • Test.java
/**
 * @Description 外观模式测试类(抽象外观类)
 */
public class Test {
    public static void main(String[] args) {
        AbstractFacade backUpFacade = (AbstractFacade) PropertiesUtil.getFacadePatternBean();
        backUpFacade.unifiedBackUp();
    }
}
  • 由于业务系统中都是输出的伪代码,就不贴了,测试输出如下:
在线音乐备份成功
在线照片备份成功
通讯录备份成功
  • 类图如下:

在这里插入图片描述


总结

  • 优点
1.简化调用过程,无需深入了解子系统,防止带来风险
2.减少系统依赖,松散耦合
3.更好的划分访问层次
4.符合迪米特法则,即最少知道原则
  • 缺点
1.增加子系统,扩展子系统行为容易引入风险
2.不符合开闭原则
  • 适用场景
1.要为访问一系列复杂的子系统提供一个简单入口时
2.客户端程序与多个子系统之间存在很大的依赖性
3.构建多层系统架构,利用外观对象作为每层的入口,简化层间调用
  • 外观模式源代码
JdbcUtils(SpringJdbc)、Configuration(mybatis)、RequestFacade(Tomcat)

源码


- End -
- 个人学习笔记 -
- 仅供参考 -

posted @ 2022-03-31 07:17  Maggieq8324  阅读(35)  评论(0编辑  收藏  举报