外观模式的基本概念
首先,我们要先了解一下外观模式的基本概念。那到底什么是外观模式?其实就是基于很多模块或者很多子系统提供的一个最高层的接口,控制客户端访问我们的应用程序,减少客户端直接对内部应用程序的调用。也可以说是客户端和应用程序之间的第一道桥梁或者一个简化过的调用模版。我从网上偷了一张图,大家可以看一下:
如果不使用外观模式的话,我们通常是Client端直接调用具体的模块,这样子系统的具体API就会暴露在客户端,而且调用起来的处理逻辑都要客户端去处理,很明显这违反了开闭原则,程序的扩展性非常的差。如果客户端的逻辑需要改变,则可能客户端的逻辑需要重新编码。如果使用的是外观模式,则可以把这些变动放在外观类中,客户端无需修改,代码的整体上扩展性更强,代码更具有健壮性。
具体的举两个具体的例子,我们生活中大家都去组装过台式电脑,电脑的零配件包括CPU,显卡,显示器等等这些。我们从市场上买了材料,我们需要自己组装,拿着一堆零件狂装一通,最后装好了,开机使用,发现根本开不了机,结果发现显卡的线差错了。如果从代码的角度来看的话,电脑的零配件相当于我们的模块或者说是子系统,而我们组装电脑的过程相当于客户端,我们进行一通调用,结果发现我们需要调用的逻辑跟我们其他的业务逻辑代码写在一起,结果调用的时候出问题了,也就是我们前面说的电脑无法开机,这个时候我们又是debug调试,打日志观察结果,这样下来,一天过去了,然而今天的工作量基本为零。如果我们把电脑交给装配店去组装,我们直接拿过来用,不仅简洁,而且效率更高,不需要我们具体的去一步一步实现,事半功倍。我们代码中如果能直接调用这一系列的逻辑和API,我们代码会非常的容易维护和调试,下面这个小例子的代码贴上来:
Cpu的接口:
package com.bane.example1.computer; /** * * @ClassName: CpuExecutor * @Description: TODO(组装电脑的CPU) * @author 于继伟 * @date 2017-10-21 下午10:49:57 * */ public interface CpuExecutor { void cpu(); }
显示器的接口:
package com.bane.example1.computer; /** * * @ClassName: ViewExecutor * @Description: TODO(组装显示器的接口) * @author 于继伟 * @date 2017-10-21 下午10:51:09 * */ public interface ViewExecutor { void view(); }
Cpu的具体实现:
package com.bane.example1.computer.impl; import com.bane.example1.computer.CpuExecutor; /** * * @ClassName: CpuExecutorImpl * @Description: TODO(CPU具体实现) * @author 于继伟 * @date 2017-10-21 下午10:52:16 * */ public class CpuExecutorImpl implements CpuExecutor{ @Override public void cpu() { System.out.println("组装CPU"); } }
显示器的具体实现:
package com.bane.example1.computer.impl; import com.bane.example1.computer.ViewExecutor; /** * * @ClassName: ViewExecutorImpl * @Description: TODO(View具体实现) * @author 于继伟 * @date 2017-10-21 下午10:52:44 * */ public class ViewExecutorImpl implements ViewExecutor{ @Override public void view() { System.out.println("组装显示器"); } }
组装电脑的外观类:
package com.bane.example1.computer.impl; import com.bane.example1.computer.ViewExecutor; /** * * @ClassName: ViewExecutorImpl * @Description: TODO(View具体实现) * @author 于继伟 * @date 2017-10-21 下午10:52:44 * */ public class ViewExecutorImpl implements ViewExecutor{ @Override public void view() { System.out.println("组装显示器"); } }
主方法测试:
package com.bane.example1; import com.bane.example1.facade.ComputerFacade; /** * * @ClassName: Main * @Description: TODO(主方法测试类) * @author 于继伟 * @date 2017-10-21 下午10:47:29 * */ public class Main { public static void main(String[] args) { ComputerFacade computerFacade = new ComputerFacade(); computerFacade.computer(); } }
控制台输出:
组装CPU
组装显示器
假如我们现在需要写这样一个工具,就是一键生成从控制层到Dao层的基础代码,减少我们基础代码的开发工作。也就是说我们这个工具有四个模块,一个生成控制层代码的模块,一个生成业务层代码的模块,一个生成Dao层代码的模块,以及我们生成代码的配置模块。按照我们前面所说的,这里非常适合使用外观模式。使用发一个外观类,去控制和管理这一系列的模块来完成我们的生成代码的功能。话不多说,我们直接上代码:
配置模块的代码如下:
package com.bane.example2.config; /** * * @ClassName: ConfigModel * @Description: TODO(生成代码的配置基类) * @author 于继伟 * @date 2017-10-22 上午9:57:06 * */ public class ConfigModel { /** * 是否需要生成表现层,默认是true */ private boolean needGenPresentation = true; /** * 是否需要生成逻辑层,默认是true */ private boolean needGenBusiness = true; /** * 是否需要生成Dao,默认是true */ private boolean needGenDao = true; public boolean isNeedGenPresentation() { return needGenPresentation; } public void setNeedGenPresentation(boolean needGenPresentation) { this.needGenPresentation = needGenPresentation; } public boolean isNeedGenBusiness() { return needGenBusiness; } public void setNeedGenBusiness(boolean needGenBusiness) { this.needGenBusiness = needGenBusiness; } public boolean isNeedGenDao() { return needGenDao; } public void setNeedGenDao(boolean needGenDao) { this.needGenDao = needGenDao; } }
package com.bane.example2.config; /** * * @ClassName: ConfigManager * @Description: TODO(示意配置管理,就是负责读取配置文件,并且把文件的内容设置到配置Model中去,是个单例) * @author 于继伟 * @date 2017-10-22 上午9:59:54 * */ public class ConfigManager { private static ConfigManager manager = null; private static ConfigModel cm = null; private ConfigManager(){ } public static ConfigManager getInstance(){ if(manager == null){ manager = new ConfigManager(); cm = new ConfigModel(); //读取配置文件,把值设置到ConfigModel中去 } return manager; } /** * 获取配置的数据 * @return */ public ConfigModel getConfigData(){ return cm; } }
具体的生成代码的模块:
package com.bane.example2.executor; /** * * @ClassName: BaseExecutor * @Description: TODO(模块生成的基类) * @author 于继伟 * @date 2017-10-22 上午10:55:15 * */ public abstract class BaseExecutor { public abstract void generate(); }
package com.bane.example2.executor; import com.bane.example2.config.ConfigManager; import com.bane.example2.config.ConfigModel; /** * * @ClassName: Presentation * @Description: TODO(生成表现层的代码) * @author 于继伟 * @date 2017-10-22 上午10:06:05 * */ public class Presentation extends BaseExecutor { public void generate(){ //1:从配置管理里面获取相应的配置信息 ConfigModel cm = ConfigManager.getInstance().getConfigData(); if(cm.isNeedGenPresentation()){ //2:按照要求去生成相应的代码,并保存文件 System.out.println("正在生成表现层代码文件"); } } }
package com.bane.example2.executor; import com.bane.example2.config.ConfigManager; import com.bane.example2.config.ConfigModel; /** * * @ClassName: Business * @Description: TODO(生成业务层的代码文件) * @author 于继伟 * @date 2017-10-22 上午10:08:37 * */ public class Business extends BaseExecutor{ public void generate(){ //1:从配置管理里面获取相应的配置信息 ConfigModel cm = ConfigManager.getInstance().getConfigData(); if(cm.isNeedGenBusiness()){ //2:按照要求去生成相应的代码,并保存文件 System.out.println("正在生成业务层代码"); } } }
package com.bane.example2.executor; import com.bane.example2.config.ConfigManager; import com.bane.example2.config.ConfigModel; /** * * @ClassName: Dao * @Description: TODO(生成Dao层代码) * @author 于继伟 * @date 2017-10-22 上午10:10:51 * */ public class Dao extends BaseExecutor{ public void generate(){ //1:从配置管理里面获取相应的配置信息 ConfigModel cm = ConfigManager.getInstance().getConfigData(); if(cm.isNeedGenPresentation()){ //2:按照要求去生成相应的代码,并保存文件 System.out.println("正在生成表现层代码文件"); } } }
最后是我们的外观类:
package com.bane.example2.facade; import com.bane.example2.executor.Business; import com.bane.example2.executor.Dao; import com.bane.example2.executor.Presentation; /** * * @ClassName: Facade * @Description: TODO(生成代码的外观类) * @author 于继伟 * @date 2017-10-22 上午9:54:09 * */ public class Facade { /** * 构造方法私有化 */ private Facade(){ } /** * 集中调用子系统的方法 */ public static void generate(){ new Presentation().generate(); new Business().generate(); new Dao().generate(); } }
主方法测试:
package com.bane.example2; import com.bane.example2.facade.Facade; /** * * @ClassName: Main * @Description: TODO(主方法测试) * @author 于继伟 * @date 2017-10-22 上午10:56:22 * */ public class Main { public static void main(String[] args) { Facade.generate(); } }
控制台输出:
正在生成表现层代码文件
正在生成业务层代码
正在生成表现层代码文件
从上面可以看出,我们的外观类的generate方法,管理着我们生成代码的一整套API,我们需要的时候直接调用就好,而配置管理模块则控制着我们代码生成的结果,不放在外观类中使用,这个模块相当于是一个管理模块配置功能,是为其他三个模块服务的。结合上一篇博客的工厂模式,我们是不是可以用工厂模式和外观模式结合起来搞一点事情呢?直接上代码:
其余的代码和上面都是一样的 我就不贴出来了,只是增加了一个工厂类,代码如下:
package com.bane.example3.factory; import com.bane.example3.facade.Facade; /** * * @ClassName: FacadeFactory * @Description: TODO(外观模式工厂) * @author 于继伟 * @date 2017-10-22 上午10:19:09 * */ public class FacadeFactory { private FacadeFactory(){ } public static Facade createFacade(){ return new Facade(); } }
主方法测试:
package com.bane.example3; import com.bane.example3.facade.Facade; import com.bane.example3.factory.FacadeFactory; /** * * @ClassName: Main * @Description: TODO(主方法测试) * @author 于继伟 * @date 2017-10-22 上午10:16:12 * */ public class Main { public static void main(String[] args) { Facade facade = FacadeFactory.createFacade(); facade.generate(); } }
控制台输出:
正在生成表现层代码文件
正在生成业务层代码
正在生成表现层代码文件
结合我们昨天所说的,使用工厂模式的好处是什么?是为了方便模块化管理,管理我们项目中一些具体类的行为以及他们之间的相互关系,现在我们是有一个生成代码的外观类,如果我们还有一个就像前面所说的组装电脑的模块,两个风马牛不相及的内容,可以使用工厂进行解耦。这样我们项目的开发和维护会变得更简单。
外观模式的优点和缺点以及总结
说了这么多,那么外观模式的有点和缺点是什么?
1.松散耦合
2.简单易用
3.更好的划分层次
4.过多或者是不太合理的Facade也容易让人迷惑
外观模式的本质是什么?
封装交互,简化调用
多设计原则的体现?
体现了最少知识原则,也就是说我们不需要知道子系统或者模块的API,体现了一个黑盒的思想
那么我们应该什么时候使用外观模式?
1.如果你希望一个复杂的子系统提供一个简单接口的时候,可以考虑使用外观模式,使用外观对象来实现大部分客户需要的功能,从而简化客户的使用。
2.如果想要让客户程序和抽象类的实现部分松散耦合,可以考虑使用外观模式,使用外观对象来实现这个子系统与它的客户分离开来,从而提高子系统的独立性和可移植性
3.如果构建多层结构的系统,可以考虑使用外观模式,使用外观对象作为每层的入口,这样可以简化层间调用,也可以松散层次之间的依赖关系。