外观模式,或者叫门面模式,是一种十分常用的结构性模式。具体体现在我们平常使用的第三方 SDK 上,在使用的过程中,可以发现这些 SDK 具有良好的封装性,我们在调用时是需要调用相关的方法就可以实现我们想要的功能,但是这个方法内部的具体实现我们一概不知,降低了我们的使用成本。简单的一句话总结就是:外观模式提供了一个高层次的接口,使得子系统更易于使用。
举一个生活中的例子,在我看来,去餐厅点餐就用到了外观模式。菜单就像是一个高层次的接口,将各种菜系和菜品都罗列在一个菜单上,用户根据自己的情况来点菜,但是用户不需要知道这个菜是怎么做吃来的,他只需要在点完菜后等待就可以了,而具体的如何做出用户点的菜由厨师来实现,这里厨师就相当于是一个一个的子系统。因此,用户通过菜单来决定吃什么,比直接去问厨师要简单的多。
- Client:客户
- Facade:高层次的接口
- SubSystem:各个子系统,由 Facade 同一管理
根据前面提到的菜单的例子,那么就以菜单为例,实现一个简单的外观模式。首先是分析一下那个是高层接口,哪些是子系统?
- 菜单通过统一管理菜系和菜品名,来向用户展示这家餐厅有什么菜,所以菜单类应该为高层接口
- 各系菜系都有不同的实现,而且都属于同一家餐厅,所以各系菜系为子系统(或者说做菜的厨师也可以)
经过简单的分析过后,下面来实现一下这个案例
首先应该是实现各个子系统接口,这里假设这家餐厅有中,法,意三个菜系(那这家餐厅到底是中餐厅还是西餐厅???)
中国菜接口:ChineseCuisine
public interface ChineseCuisine {
/* 白切鸡 */
public void boiledChickenwithSauce();
/* 铁板牛肉 */
public void sizzlingBeefSteak();
/* 宫保鸡丁 */
public void kungPaoChicken();
}
法国菜接口:FrenchCuisine
public interface FrenchCuisine {
/* 马赛鱼汤 */
public void bouillabaisse();
/* 豆焖肉 */
public void cassoulet();
/* 法式炖鸡 */
public void pouleAuPot();
}
意大利接口:ItalyCuisine
public interface ItalyCuisine {
/* 焗茄汁千层面 */
public void lasagneWithTomatoAndCheese();
/* 虾仁烩饭 */
public void prawnRisotto();
/* 焦糖布丁 */
public void creamCaramel();
}
接口内部只是简单的罗列了一下,接下来就是具体的实现了
中国菜实现类:ChineseCuisineImpl
public class ChineseCuisineImpl implements ChineseCuisine {
@Override
public void boiledChickenWithSauce() {
/* 准备材料; 加工材料; 烹饪; 装盘 */
System.out.println("白切鸡");
}
@Override
public void sizzlingBeefSteak() {
/* 准备材料; 加工材料; 烹饪; 装盘 */
System.out.println("铁板牛肉");
}
@Override
public void kungPaoChicken() {
/* 准备材料; 加工材料; 烹饪; 装盘 */
System.out.println("宫保鸡丁");
}
}
法国菜实现类:FrenchCuisineImpl
public class FrenchCuisineImpl implements FrenchCuisine {
@Override
public void bouillabaisse() {
System.out.println("马赛鱼汤");
}
@Override
public void cassoulet() {
System.out.println("豆焖肉");
}
@Override
public void pouleAuPot() {
System.out.println("法式炖鸡");
}
}
意大利菜实现类:ItalyCuisineImpl
public class ItalyCuisineImpl implements ItalyCuisine {
@Override
public void lasagneWithTomatoAndCheese() {
System.out.println("焗茄汁千层面");
}
@Override
public void prawnRisotto() {
System.out.println("虾仁烩饭");
}
@Override
public void creamCaramel() {
System.out.println("焦糖布丁");
}
}
注意 关于具体实现的细节,这里是用注释加以代替,但在实际开发中,应该根据具体情况而定
以上,菜就算“做”好了,下面该实现菜单类了
菜单类:Menu
public class Menu {
private ChineseCuisine chineseCuisine;
private FrenchCuisine frenchCuisine;
private ItalyCuisine italyCuisine;
public Menu() {
chineseCuisine = new ChineseCuisineImpl();
frenchCuisine = new FrenchCuisineImpl();
italyCuisine = new ItalyCuisineImpl();
}
public void boiledChickenWithSauce() {
chineseCuisine.boiledChickenWithSauce();
}
public void sizzlingBeefSteak() {
chineseCuisine.sizzlingBeefSteak();
}
public void kungPaoChicken() {
chineseCuisine.kungPaoChicken();
}
public void bouillabaisse() {
frenchCuisine.bouillabaisse();
}
public void cassoulet() {
frenchCuisine.cassoulet();
}
public void pouleAuPot() {
frenchCuisine.cassoulet();
}
public void lasagneWithTomatoAndCheese() {
italyCuisine.lasagneWithTomatoAndCheese();
}
public void prawnRisotto() {
italyCuisine.prawnRisotto();
}
public void creamCaramel() {
italyCuisine.creamCaramel();
}
}
经过以上的步骤过后,就可以简单的测试一下了
Menu menu = new Menu();
System.out.println("客户一点餐:");
menu.boiledChickenWithSauce();
menu.lasagneWithTomatoAndCheese();
menu.sizzlingBeefSteak();
System.out.println("\n+-----------+\n");
System.out.println("客户二点餐:");
menu.cassoulet();
menu.pouleAuPot();
menu.creamCaramel();
测试结果如下
客户一点餐:
白切鸡
焗茄汁千层面
铁板牛肉
+-----------+
客户二点餐:
豆焖肉
法式炖鸡
焦糖布丁
可见,Menu 的作用就是同一管理各个菜,通过一份菜单客户就可以点到各种各样的菜,降低了用户的使用(点餐)成本,使用起来更加简单
通过将各个子系统封装起来,并通过一个高层次的结构向用户提供一个统一的 API 入口,使得这些子系统的使用更加的灵活并且降低了用户的使用成本。所以这里总结一下外观模式的优缺点:
-
优点:向使用者或者用户隐藏具体实现细节,减少对子系统的联系,通过统一的管理,使得子系统易于使用
-
缺点:因为管理着多个子系统,因此对于外观类来说,并没有遵循开闭原则,所以一旦业务出现变更,就需要直接修改外观类来适应变更
END.