简介
- 定义:又叫门面模式,提供一个统一的接口,用来访问子系统中的一群接口。
- 解释:外观模式定义了一个高层接口,让子系统更容易被使用。
- 类型:结构型
- 适用场景:
- 子系统越来越复杂,外观模式能够提供简单的调用接口。
- 构建多层系统结构,利用外观对象作为每层的入口,简化层间调用。
- 优点:
- 简化调用过程,无需了解深入子系统,防止带来风险
- 减少系统依赖,松散耦合
- 更好地划分访问层次
- 符合迪米特法则,即最少知道原则
- 缺点:
- 增加,扩展子系统的行为容易引入风险
- 不符合开闭原则
- 相关设计模式
- 外观模式和中介模式
- 外观模式和单例模式
- 外观模式和抽象工厂模式
代码实现
业务场景:在网上书店,我们假定购买一本书系统需要经过三个流程:
- 身份校验(CheckUser)。检测用户是否登陆,没有登陆则强制要求用户先登录
- 支付校验(Payment)。检查金额数量,余额等,并进行在线支付。
- 物流系统(Logistics)。支付成功后,交由物流系统处理,并实时显示物流信息等操作。
下面是相关的业务流程示意图:
从上面示意图,很容易理解外观模式中所谓的 “又称为门面模式,它提供一个统一的接口,用来访问子系统中的一群接口” 。各个service就是底层子系统的一群接口。这个”门面“既对外保持访问子系统的统一接口,对内又能访问子系统中的一群接口。
下面查看具体代码实现:
首先是最简单的两个实体类User,Book,为了展现外观设计模式,对具体的实现作了一定的简化。
用户类: 很简单的实现,仅仅定义两个简单字段isLogin,remainingMoney分别表示用户登录状态和账户余额。
/**
* 用户类。
* 这里为演示方便,做简单处理:定义两个字段。
* isLogin:标识用户是否登陆
* remainingMoney:标识用户的余额
*/
public class User {
// 用户登录标志。这里默认用户是登陆的
public static boolean isLogin = true;
// 简单初始化用户的余额为 46 元
private static double remainingMoney = 46;
public static double getRemainingMoney() {
return remainingMoney;
}
public static void consume(double price){
if( price > remainingMoney ){
throw new RuntimeException("消费金额不能大于余额");
}
remainingMoney -= price;
}
}
定义简单的商品类,因为我们的业务场景是网上书店。定义一个Book类。同样简单起见我们只使用了两本书作为例子演示。一本书大于用户的默认余额,另外一本书小于用户的默认余额。
/**
* 定义一个商品类。
* 简单地定义两个字段:商品名称,价格
*/
public class Book {
private String name;
private double price;
/*
getter 和 setter 方法
*/
public Book(String name){
this.name = name;
//为简单起见,如下方式定义书的价格
if( "设计模式".equals(name) ) {
this.price = 35;
} else if( "算法设计".equals(name) ) {
this.price = 49;
} else {
//默认书的价格46
this.price = 46;
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
下面直接查看测试类Test。从整体上查看这个外观的接口:
public class Test {
public static void main(String[] args) {
// 业务场景:现在我买了一本书,它叫《设计模式》(它35元)
// 1. 买一本书:
Book book = new Book("设计模式");
//Book book1 = new Book("算法设计");
// 2. 提交系统。系统进行购物逻辑处理
int resCode = ShoppingService.shopping(book);
//int resCode2 = ShoppingService.shopping(book1);
// 下面是对购物中系统内部出现的错误,进行相应的处理。
// 这里就不具体实现了
}
}
可以看到,这跟现实生活中的例子一样:我们只需要接触我们所买的书——
new Book("设计模式")
,然后直接提交给购物系统就行了——ShoppingService.shopping(book)
。
下面查看外观模式中的对客户端的外观。也就是ShoppingService类。
/**
* 定义子系统(Shopping购物系统)的外观模式类。
* 外面通过这个类的接口就可以实现系统的功能了。
*
* 外界通过shopping的返回值能够确定shopping流程的一些错误
*/
public class ShoppingService {
public static final int LOGIN_FAiLURE = 0; // 登陆失败
public static final int SUCCESS = 1; // 购买成功
public static final int PAYMENT_FAILURE = 2; // 支付失败
public static final int LOGISTICS_FAITURE = 3; // 物流处理故障
// 验证用户身份接口服务
private static CheckUserService checkUserService = new CheckUserService();
// 支付接口服务
private static PaymentService paymentService = new PaymentService();
// 物流接口服务
private static LogisticsService logisticsService = new LogisticsService();
// 对外的唯一接口
public static int shopping(Book shopItem){
// 1. 检查用户身份(登陆状态)
if( checkUserService.isLogin() ){
System.out.println("用户是登陆的.");
// 2. 支付
if( paymentService.pay( shopItem ) ) {
// 3. 物流
if( logisticsService.handleLogistics() ){
System.out.println("购物成功");
//物流处理顺利,这里我们直接返回正确代码:ShoppingService.SUCCESS
return ShoppingService.SUCCESS;
} else { // 物流有错,返回错误代码
System.out.println("物流出错啦.");
return ShoppingService.LOGISTICS_FAITURE;
}
} else { // 支付失败,返回错误代码
System.out.println("支付失败, 余额不足.");
return ShoppingService.PAYMENT_FAILURE;
}
} else {
// 用户没登陆,返回错误代码
System.out.println("用户登录失败.");
return ShoppingService.LOGIN_FAiLURE;
}
}
}
暂时不讨论各个service成员的具体实现。我们这个外观类中保存了子系统中的所有接口,把它们作为类的成员变量。 然后外部调用此外观类的接口(这里是shopping函数),ShoppingService 外观类在处理外部请求任务的逻辑中,充分的利用了已经保存的子系统的一系列接口,实现相关逻辑。。
值得说明的是,shopping函数返回的代码:0,1,2,3与外观模式关系不大,不过对于很好的理解此类业务逻辑有帮助。
下面是子系统内部的三个接口的实现逻辑:
CheckUserService类检查用户是否登陆。
public class CheckUserService {
/**
* 简单验证用户身份:用户是否登陆
* @return true/false 登陆与否
*/
public boolean isLogin(){
return User.isLogin;
}
}
PaymentService类实现支付逻辑
/**
* 支付服务,PaymentService
*/
public class PaymentService {
public boolean pay(Book shopItem){
// 如果余额还够的话,就能够进行支付
if( shopItem.getPrice() <= User.getRemainingMoney() ){
System.out.println("支付中...");
User.consume( shopItem.getPrice() );
System.out.println("支付成功...");
// 支付成功,返回true;
return true;
}
// 余额不足,支付失败,返回false
return false;
}
}
LogisticsService类实现物流相关的逻辑,这里仅仅简单的表示一下。
/**
* 物流服务类。
* 完成支付后,处理物流相关逻辑。
*/
public class LogisticsService {
//处理物流,为演示方面直接返回true
public boolean handleLogistics(){
System.out.println("处理物流...");
return true;
}
}
UML图
下面是这个代码工程项目的UML图:
总结
值得注意的是,上面的代码分析是自顶向下的方式(自客户端代码实现到底层代码实现,自Test类到外观类再到子系统的接口群)。