外观模式--导读
在生活中,我们都会碰到想做一件事之前要做好一系列的准备工作,只有当这一系列的准备工作才能做好自己想要做的那件事,就像我们自己泡茶一样,首先我们要准备好茶叶,准备好杯子还要准备好开水,然后将开水放入到装好茶叶的杯子中。就这样会我们就能够喝到想喝的茶了。但是人总是懒惰的,我又想喝茶又不想那一系列的准备工作,所以我们就想到了茶馆,我们只需要告诉 茶馆我们需要喝什么茶,将这一列复杂的工作交给茶馆做就行了。这样我们只需要喝茶就行了。这就是外观模式。 所谓外观模式就是提供一个统一的接口,用来访问子系统中的一群接口。 外观模式定义了一个高层接口,让子系统更容易使用。如下图,是使用外观模式后将子系统的使用变得更加简单。外观模式通过引入一个新的外观类(Facade)来实现该功能,外观类充当了软件系统中的“服务员”,它为多个业务类的调用提供了一个统一的入口,简化了类与类之间的交互。在外观模式中,那些需要交互的业务类被称为子系统(Subsystem)。如果没有外观类,那么每个客户类需要和多个子系统之间进行复杂的交互,系统的耦合度将很大
外观模式--定义
外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。
外观模式--结构
外观模式包含如下两个角色:
Facade: 外观角色
SubSystem:子系统角色
外观模式--代码实现
1.简单实现
下面我以泡茶为例子进行代码实现
package FacadePattern; /** * 根据用户的需求来取茶叶 * @author xyxy001 * */ public class GetLeaf { public static void getLeaf(String type){ System.out.println("获取到"+type+"茶叶"); } }
package FacadePattern; public class GetTeaSet { public static void getTeaSet(){ System.out.println("拿好装茶的杯具"); } }
getWater用于作为子系统
package FacadePattern; public class GetWater { public static void getWater(){ System.out.println("将水烧开"); } }
package FacadePattern; public class MakeTea { //将茶叶放入开水中 public static void makeTea(){ System.out.println("将茶叶放到开水中"); } }
package FacadePattern; /** * 对上面一系列操作提供一个统一的调用接口 * 相当于茶馆 * @author xyxy001 * */ public class TeaHouse { //茶馆卖茶的服务员,你只需要告诉他自己想喝茶的类型就可以 public static void shopTea(String type){ //服务员拿好茶叶 GetLeaf.getLeaf(type); //服务员拿好茶具 GetTeaSet.getTeaSet(); //服务员烧好水 GetWater.getWater(); //服务员将茶叶放入开水中 MakeTea.makeTea(); System.out.println("您的茶已经泡好了"); } }
package FacadePattern; public class client { public static void main(String[] args) { //如果自己一个人在家里喝茶的话 GetLeaf.getLeaf("龙井"); GetWater.getWater(); GetTeaSet.getTeaSet(); MakeTea.makeTea(); //如果自己一个人泡茶的话整个泡茶的流程都要自己了解 System.out.println("/***********/"); //如果自己进茶馆喝茶的话 TeaHouse.shopTea("龙井"); } }
从上面例子我们可以看到,当我们不用外观模式时,客户端对泡茶,拿茶具,烧水,泡茶客户端都要自己进行。当有外观类时客户端仅需知道外观类即可,而不需要对泡茶的具体过程进行了解,从而降低了系统的耦合度。
介绍到这里我们应该对外观模式有了一定的了解了,但是你可能会发现一个问题,那就是当我们需要增加新的流程,相对应得我们的外观类也要进行改变了,但是这不符合开闭原则,所以我们提出了抽象外观类来进行改造。
2.抽象外观
在标准的外观模式结构图中,如果需要增加、删除或更换与外观类交互的子系统类,必须修改外观类或客户端的源代码,这将违背开闭原则,因此可以通过引入抽象外观类来对系统进行改进,在一定程度上可以解决该问题。在引入抽象外观类之后,客户端可以针对抽象外观类进行编程,对于新的业务需求,不需要修改原有外观类,而对应增加一个新的具体外观类,由新的具体外观类来关联新的子系统对象,同时通过修改配置文件来达到不修改任何源代码并更换外观类的目的。
于是我们做如下更改
定义一个抽象的外观类
package Facade_Pattern; public abstract class AbstractFacade { public abstract void shopTea(String type); }
新增的具体的外观类
package Facade_Pattern; /** * 现在这家高端的茶馆,要提高档次而增加原先的步骤 * @author liu * */ public class LuxuryTeaHouse extends AbstractFacade{ @Override public void shopTea(String type) { //少女准备好含茶 PrepareLeaf.prepareLeaf(type); //服务员拿好茶叶 GetLeaf.getLeaf(type); //服务员拿好茶具 GetTeaSet.getTeaSet(); //服务员烧好水 GetWater.getWater(); //服务员将茶叶放入开水中 MakeTea.makeTea(); System.out.println("您的茶已经泡好了"); } }
新增加的子步骤
package Facade_Pattern; /** * 现在为了体现茶馆的高端,提供了新步骤,少女含茶 * @author liu * */ public class PrepareLeaf { public static void prepareLeaf(String type) { System.out.println("少女含"+type+"茶完毕,准备茶叶完毕"); } }
配置文件config中存储了具体外观类的类名。
<?xml version="1.0" encoding="GBK"?> <config> <className>Facade_Pattern.LuxuryTeaHouse</className> </config>
通过反射获取具体外观类的工具类
package Facade_Pattern; import java.io.File; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; public class XMLUtil { public static Object getFacade() { try { DocumentBuilderFactory bFactory=DocumentBuilderFactory.newInstance(); DocumentBuilder db=bFactory.newDocumentBuilder(); Document doc=db.parse(new File("src//Facade_Pattern//config.xml")); NodeList nodes=doc.getElementsByTagName("className"); Node node=nodes.item(0).getFirstChild(); String name=node.getNodeValue(); Class c=Class.forName(name); Object oc=c.newInstance(); return oc; } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } }
客户端的调用
package Facade_Pattern; public class client { public static void main(String[] args) { //如果自己一个人在家里喝茶的话 GetLeaf.getLeaf("龙井"); GetWater.getWater(); GetTeaSet.getTeaSet(); MakeTea.makeTea(); //如果自己一个人泡茶的话整个泡茶的流程都要自己了解 System.out.println("/***********"); //如果自己进茶馆喝茶的话 TeaHouse.shopTea("龙井"); System.out.println("/***********"); //当我需要更改外观接口时,仅仅需要改变配置文件和增加新的外观接口,而不需要更改原先的接口 //新的抽象外观接口 AbstractFacade newFacade=(AbstractFacade)XMLUtil.getFacade(); newFacade.shopTea("龙井"); } }
原有外观类TeaHouse也需作为抽象外观类AbstractFacade类的子类,更换具体外观类时只需修改配置文件,无须修改源代码,符合开闭原则。
外观模式--使用场景
1、当要为一个复杂子系统提供一个简单接口时可以使用外观模式。
2、客户程序与多个子系统之间存在很大的依赖性。引入外观类将子系统与客户以及其他子系统解耦,可以提高子系统的独立性和
可移植性
外观模式--优缺点
外观模式的主要优点如下:
(1) 它对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易。通过引入外观模式,客户端代码将变得很简单,与之关联的对象也很少。
(2) 它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可。
(3) 一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。
外观模式的主要缺点如下:
(1) 不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活 性。
(2) 如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则。