1 定义
外观模式,又叫做门面模式,是指在与一个复杂的子系统进行交互时,为子系统提供一个高层的接口,转而和高层的接口进行交互,以符合迪米特法则(Law of Demeter,LoD)。
在软件设计中,当一个系统功能越来越强,代码越来越多,复杂度也越来越高,如果直接与系统交互,那么子系统发生改变,交互代码也要随着改变,这违背的开闭原则(Open Closed Principle,OCP)。此时,我们需要为子系统提供一个高层的接口,降低耦合度。
2 结构与一般实现
外观模式结构如下图所示,主要包含如下角色:
1.外观(Facade)角色:为多个子系统对外提供一个共同的接口。
2.子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
3.客户(Client)角色:通过一个外观角色访问各个子系统的功能。
3 优缺点
3.1 优点
1.降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户端。
2.对客户端屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
3.降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。
3.2 缺点
1.不能很好地限制客户使用子系统类,很容易带来未知风险。
2.增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
4 适用场景
1.对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
2.当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
3.当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。
5 与代理模式的区别
个人理解,代理模式,是功能类的代理,也就说代理类和被代理类在功能上是相同的,代理类能增强功能类的功能。比如尤尼克斯的代理,他只能经销尤尼克斯的相关产品,尤尼克斯公司卖什么代理商就卖什么,如果想要买胜利的货品,就需要去另一家胜利的代理商。
外观模式,是子系统功能的一个抽象,不代表某一个具体的子系统,是根据客户端的想要的功能,对子系统功能的一个组合。比如大型超市,卖各种各样的货品,茅台的白酒,剑南春的白酒都卖,我们想要卖各种各样的白酒,直接去超时就可以了。
6 代码示例
在饭店点餐时,我们(客户端)和饭店厨房(子系统)由服务员(外观模式)进行了解耦,我们想要点菜时,我们告诉服务员,服务员点餐好了通知厨房做菜,我们并不需要知道我们想要吃的菜是由那个厨子去做,不需要找厨子本人进行点菜。
我们现在有三个卖饭窗口,卖不同的食物,我们想要的食物可能不在某一个窗口,我们可以通过调用外观进行点菜。
6.1 饭菜窗口A MealWindowA
/**
* @program: design-pattern-learning
* @author: zgr
* @create: 2021-09-24 10:55
**/
public class MealWindowA {
public MealWindowA(){
System.out.println("食堂饭菜A窗口,主营陕西特色");
}
public void sellOilNoddles(){
System.out.println("好吃不贵的油泼面啦!");
}
public void sellSaoZiNoddles(){
System.out.println("好吃不贵的臊子面啦!");
}
public void sellSpecialNoddles(){
System.out.println("好吃不贵的Biáng Biáng面啦!");
}
}
6.2 饭菜窗口B MealWindowB
/**
* @program: design-pattern-learning
* @author: zgr
* @create: 2021-09-24 11:01
**/
public class MealWindowB {
public MealWindowB(){
System.out.println("食堂饭菜B窗口,主营四川特色");
}
public void sellFish(){
System.out.println("巴适得板的水煮鱼哈!");
}
public void sellMeat(){
System.out.println("巴适得板的水煮肉片哈!");
}
public void sellBlood(){
System.out.println("巴适得板的毛血旺哈!");
}
}
6.3 饭菜窗口C MealWindowC
/**
* @program: design-pattern-learning
* @author: zgr
* @create: 2021-09-24 10:55
**/
public class MealWindowC {
public MealWindowC(){
System.out.println("食堂饭菜C窗口,主营东北特色");
}
public void sellChickenWithMushrooms(){
System.out.println("贼拉好吃的小鸡炖蘑菇!");
}
public void sellShaZhuCai(){
System.out.println("贼拉好吃的酸菜汆白肉!");
}
public void sellGuoBaoRou(){
System.out.println("贼拉好吃的锅包肉!");
}
}
6.4 外观 MyFacade
/**
* @program: design-pattern-learning
* @author: zgr
* @create: 2021-09-24 11:12
**/
public class MyFacade {
private MealWindowA mealWindowA;
private MealWindowB mealWindowB;
private MealWindowC mealWindowC;
public MyFacade(){
this.mealWindowA = new MealWindowA();
this.mealWindowB = new MealWindowB();
this.mealWindowC = new MealWindowC();
}
public void buyMealA(){
mealWindowA.sellSaoZiNoddles();
mealWindowB.sellBlood();
mealWindowC.sellGuoBaoRou();
}
public void buyMealB(){
mealWindowB.sellMeat();
mealWindowB.sellFish();
mealWindowC.sellShaZhuCai();
}
}
6.5 主函数 MainClass
/**
* @program: design-pattern-learning
* @author: zgr
* @create: 2021-09-24 10:53
**/
public class MainClass {
public static void main(String[] args) {
MyFacade facade = new MyFacade();
System.out.println();
System.out.println();
System.out.println();
System.out.println("我是一号服务员,请点菜,我给你买");
facade.buyMealA();
System.out.println();
System.out.println("我是二号服务员,请点菜,我给你买");
facade.buyMealB();
}
}
6.6 运行结果
6.7 总结
代码可以看出,卖饭窗口ABC是一个子系统,外观是ABC子系统的一个合集,而不仅仅是代理一个窗口而已。本例代码中,外观模式只有两个功能,实际中,外观应根据客户端需要,对子系统的功能进行合集抽象,以满足功能需求。
7 引用
1.《大话设计模式》
8 源代码
https://github.com/airhonor/design-pattern-learning/tree/main/src/com/hz/design/pattern/facade