外观(Facade)
外观模式又叫门面模式,提供了一个统一的接口,用来访问子系统中的一群接口。
外观模式定义了一个高层接口,让子系统更容易使用。
适用场景:
- 子系统越来越复杂,增加外观模式提供简单调用接口
- 构建多层系统接口,利用外观对象作为每层的入口,简化层间调用
优缺点
优点:简化了调用过程,无需了解子系统,防止带来风险;减少系统依赖、松散耦合;更好的划分访问层次;符合迪米特法则,即最少知道原则。
缺点:增加子系统、扩展子系统行为容易引入风险,不符合开闭原则。
应用场景
我们考虑一个用积分兑换礼物的场景,积分兑换礼物需要校验积分是否符合资格、扣减积分以及对接物流系统三个模块,这三个模块也可以理解为三个子系统。
校验资格子系统:
public class QualifyService {
public boolean isAvailable(PointsGift pointsGift){
System.out.println("校验" + pointsGift.getName() + " 积分资格通过,库存通过");
return true;
}
}
扣减积分子系统:
public class PointsPaymentService {
public boolean pay(PointsGift pointsGift){
//扣减积分
System.out.println("支付" + pointsGift.getName() + " 积分成功");
return true;
}
}
对接物流系统的子系统:
public class ShippingService {
public String shipGift(PointsGift pointsGift){
//物流系统的对接逻辑
System.out.println(pointsGift.getName() + "进入物流系统");
String shippingOrderNo = "666";
return shippingOrderNo;
}
}
积分礼物类:
public class PointsGift {
private String name;
public PointsGift(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
外观类:
public class GiftExchangeService {
private QualifyService qualifyService = new QualifyService();
private PointsPaymentService pointsPaymentService = new PointsPaymentService();
private ShippingService shippingService = new ShippingService();
public QualifyService getQualifyService() {
return qualifyService;
}
public void setQualifyService(QualifyService qualifyService) {
this.qualifyService = qualifyService;
}
public PointsPaymentService getPointsPaymentService() {
return pointsPaymentService;
}
public void setPointsPaymentService(PointsPaymentService pointsPaymentService) {
this.pointsPaymentService = pointsPaymentService;
}
public ShippingService getShippingService() {
return shippingService;
}
public void setShippingService(ShippingService shippingService) {
this.shippingService = shippingService;
}
public void giftExchange(PointsGift pointsGift){
if(qualifyService.isAvailable(pointsGift)){
//资格校验通过
if(pointsPaymentService.pay(pointsGift)){
//如果支付积分成功
String shippingOrderNo = shippingService.shipGift(pointsGift);
System.out.println("物流系统下单成功,订单号是:"+shippingOrderNo);
}
}
}
}
客户端类:
public class Test {
public static void main(String[] args) {
PointsGift pointsGift = new PointsGift("衣服");
GiftExchangeService giftExchangeService = new GiftExchangeService();
giftExchangeService.giftExchange(pointsGift);
}
}
输出:
校验衣服 积分资格通过,库存通过
支付衣服 积分成功
衣服进入物流系统
物流系统下单成功,订单号是:666
客户端创建一个衣服作为积分商品,然后使用积分兑换系统来完成积分兑换,这个积分兑换系统作为一个外观类整合了各个子系统,而客户端无需知道具体的子系统。
Spring中的应用
查看org.springframework.jdbc.support
下的JdbcUtils
:
public abstract class JdbcUtils {
public static void closeConnection(Connection con) {
if (con != null) {
try {
con.close();
}
catch (SQLException ex) {
logger.debug("Could not close JDBC Connection", ex);
}
catch (Throwable ex) {
// We don't trust the JDBC driver: It might throw RuntimeException or Error.
logger.debug("Unexpected exception on closing JDBC Connection", ex);
}
}
}
public static Object getResultSetValue(ResultSet rs, int index, Class<?> requiredType) throws SQLException {
if (requiredType == null) {
return getResultSetValue(rs, index);
}
Object value = null;
boolean wasNullCheck = false;
// Explicitly extract typed value, as far as possible.
if (String.class.equals(requiredType)) {
value = rs.getString(index);
}
else if (boolean.class.equals(requiredType) || Boolean.class.equals(requiredType)) {
value = rs.getBoolean(index);
wasNullCheck = true;
}
else if (byte.class.equals(requiredType) || Byte.class.equals(requiredType)) {
value = rs.getByte(index);
wasNullCheck = true;
}
else if (short.class.equals(requiredType) || Short.class.equals(requiredType)) {
value = rs.getShort(index);
wasNullCheck = true;
}
else if (int.class.equals(requiredType) || Integer.class.equals(requiredType)) {
value = rs.getInt(index);
wasNullCheck = true;
}
else if (long.class.equals(requiredType) || Long.class.equals(requiredType)) {
value = rs.getLong(index);
wasNullCheck = true;
}
else if (float.class.equals(requiredType) || Float.class.equals(requiredType)) {
value = rs.getFloat(index);
wasNullCheck = true;
}
else if (double.class.equals(requiredType) || Double.class.equals(requiredType) ||
Number.class.equals(requiredType)) {
value = rs.getDouble(index);
wasNullCheck = true;
}
else if (byte[].class.equals(requiredType)) {
value = rs.getBytes(index);
}
else if (java.sql.Date.class.equals(requiredType)) {
value = rs.getDate(index);
}
else if (java.sql.Time.class.equals(requiredType)) {
value = rs.getTime(index);
}
else if (java.sql.Timestamp.class.equals(requiredType) || java.util.Date.class.equals(requiredType)) {
value = rs.getTimestamp(index);
}
else if (BigDecimal.class.equals(requiredType)) {
value = rs.getBigDecimal(index);
}
else if (Blob.class.equals(requiredType)) {
value = rs.getBlob(index);
}
else if (Clob.class.equals(requiredType)) {
value = rs.getClob(index);
}
else {
// Some unknown type desired -> rely on getObject.
value = getResultSetValue(rs, index);
}
// Perform was-null check if demanded (for results that the
// JDBC driver returns as primitives).
if (wasNullCheck && value != null && rs.wasNull()) {
value = null;
}
return value;
}
可以看出,该工具类主要是对jdbc的封装,向外提供一个隐藏了具体实现细节的接口,对访问屏蔽复杂的子系统调用。
SLF4J中的应用
SLF4J是简单的日志外观模式框架,抽象了各种日志框架例如Logback、Log4j、Commons-logging和JDK自带的logging实现接口。它使得用户可以在部署时使用自己想要的日志框架。
SLF4J没有替代任何日志框架,它仅仅是标准日志框架的外观模式。如果在类路径下除了SLF4J再没有任何日志框架,那么默认状态是在控制台输出日志。
参考资料
- 弗里曼. Head First 设计模式 [M]. 中国电力出版社, 2007.
- 慕课网java设计模式精讲 Debug 方式+内存分析
- SLF4J和Logback日志框架详解