设计模式系列文章:https://zhum.in/blog/category/notes/design-pattern
Demo code:https://github.com/njZhuMin/BlogSampleCode
门面模式
门面模式 (Facade Pattern) 又称外观模式,提供了一个统一的高层次接口,用来访问子系统中的一群接口。
应用场景:
- 当子系统越来越复杂时,使用门面模式提供统一简单的入口接口。(横向耦合)
- 构建多层系统结构,利用门面对象作为每层的入口,简化层间调用。(纵向耦合)
应用场景
门面模式理解起来比较简单,就直接用一段购物的逻辑代码来展示一下:
public class Product {
public String productName;
public float price;
public Product(String productName, float price) {
this.productName = productName;
this.price = price;
}
}
public class StockService {
public static boolean isAvailable(Product product) {
System.out.println("Product " + product.productName + " is available.");
return true;
}
}
public class PaymentService {
public static boolean pay(Product product) {
System.out.println("Payment success, price = " + product.price);
return true;
}
}
public class DeliveryService {
public static boolean deliver(Product product) {
System.out.println("Product " + product.productName + " delivered, delivery number: 01002233");
return true;
}
}
当用户进行一次完成的购物流程时,不使用门面模式,客户端需要处理复杂的逻辑嵌套:
public class Client {
public static void main(String[] args) {
Product book = new Product("《Learn Design Pattern》", 50.0f);
System.out.println("[Without facade interface]");
buyProductStepByStep(book);
}
private static void buyProductStepByStep(Product product) {
boolean checkAvailable = StockService.isAvailable(product);
if (checkAvailable) {
boolean paymentSuccess = PaymentService.pay(product);
if (paymentSuccess) {
boolean delivery = DeliveryService.deliver(product);
if (delivery) {
System.out.println("Buy " + product.productName + " success.");
}
}
}
}
}
而在后端使用门面模式对购物逻辑进行包装后,就可以使调用逻辑变得更加简单清晰:
public class Client {
public static void main(String[] args) {
Product book = new Product("《Learn Design Pattern》", 50.0f);
System.out.println("[With facade interface]");
buyProductWithOrderFacade(book);
}
private static void buyProductWithOrderFacade(Product product) {
boolean result = OrderFacadeService.buyProduct(product);
if (result) {
System.out.println("Buy " + product.productName + " success.");
}
}
}
依赖关系如下:
我们日常使用的框架、工具中也有很多基于门面模式的包装,如
- Spring-JDBC:
JdbcUtils
类中的closeConnection
,closeStatement
,closeResultSet
等方法 - MyBatis:
Configuration
类中的newMetaObject
,newStatementHandler
,newExecutor
等方法 - Tomcat:
RequestFacade
类,ResponseFacade
,StandardSessionFacade
- Controller 层对 Service 层的逻辑包装
门面模式与代理模式,单例模式
- 门面模式是一种特殊的,没有代码增强的静态代理模式。
- 门面模式的重点在于横向的逻辑封装,代理模式重点在于对原有对象的逻辑增强。
- 在一些场景下,我们通常会将门面模式实现为单例模式,如工具类。
小结
门面模式的优点:
- 简化了调用过程,无需深入了解子系统,减小对子系统带来的风险
- 减少系统依赖,降低耦合
- 更好地划分逻辑层次,提高了系统安全性
- 遵循迪米特法则,即最少知道原则
缺点:
- 当子系统进行扩展或修改时,需要修改门面接口代码,可能带来未知风险
- 不符合开不原则
- 过于复杂的门面模式违背了单一职责原则(不强制实现接口,也不是面向接口编程)
装饰器模式
装饰器模式可以透明的,动态的扩展对象的功能
装饰器模式 (Decorator Pattern) 也成包装模式 (Wrapper Pattern),核心是在不改变原有对象的基础上,将功能附加到对象上,提供了比继承更有弹性的扩展原有对象功能的方案。
其实现原理为:
- 让装饰器实现被包装类 (Concrete Component) 相同的接口 (Component),使得装饰器与被扩展类类型一致。
- 在装饰器类的构造函数中传入该接口 (Component) 对象,然后就可以在接口定义的方法中,在被包装类的对象的现有功能上添加新功能了。
- 由于装饰器与被包装类属于同一类型 (均实现 Component 接口),且构造函数的参数为其实现接口类 (Component),因此装饰器模式具备嵌套扩展功能。
使用场景:
- 用于扩展一个类的功能或给一个类添加附加职责。
- 动态的给一个对象添加功能,这些功能也可以再动态撤销。
应用场景
举个例子,我们使用 slf4j-simple
打印日志:
public class Test {
private static final Logger logger = LoggerFactory.getLogger(Test.class);
public static void main(String[] args) {
logger.error("Some error happened.");
}
}
默认的输出格式:
[main] ERROR example.Decorator.Logger.Test - Some error happened.
现在我们觉得日志内容是字符串不方便解析,想要扩展支持 JSON 格式的日志输出,因此我们来装饰一下 Logger
类:
public abstract class LoggerDecorator implements Logger {
protected Logger logger;
public LoggerDecorator(Logger logger) {
this.logger = logger;
}
// override implementations
}
public class JSONLogger extends LoggerDecorator {
public JSONLogger(Logger logger) {
super(logger);
}
@Override
public void error(String s) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("message", s);
this.logger.error(jsonObject.toString());
}
public void error(Exception e) {
JsonObject result = new JsonObject();
result.addProperty("exception", e.getClass().getName());
String trace = Arrays.toString(e.getStackTrace());
result.addProperty("stackTrace", trace);
this.logger.error(result.toString());
}
}
这样我们就是实现了两个对 Logger.error
的装饰方法。测试一下新的装饰器类:
public class JSONLoggerFactory {
public static JSONLogger getLogger(Class<?> clazz) {
Logger logger = LoggerFactory.getLogger(clazz);
return new JSONLogger(logger);
}
}
public class Test {
private static final Logger logger = LoggerFactory.getLogger(Test.class);
private static final JSONLogger jsonLogger = JSONLoggerFactory.getLogger(Test.class);
public static void main(String[] args) {
logger.error("Some error happened.");
jsonLogger.error("Some error happened.");
jsonLogger.error(new IllegalArgumentException("Some exception"));
}
}
输出日志:
[main] ERROR example.Decorator.Logger.Test - Some error happened.
[main] ERROR example.Decorator.Logger.Test - {"message":"Some error happened."}
[main] ERROR example.Decorator.Logger.Test - {"exception":"java.lang.IllegalArgumentException","stackTrace":"[example.Decorator.Logger.Test.main(Test.java:14)]"}
JDK 中的装饰模式
装饰器模式在源码中也应用得非常多,在 JDK 中体现最明显的类就是 IO 相关的类,如 BufferedReader
、 InputStream
、 OutputStream
。
InputStream
的类结构图:
我们可以看出基于继承体系的装饰器模式在 JDK 中的应用:
public abstract class InputStream implements Closeable {
public InputStream() {
}
}
public class FilterInputStream extends InputStream {
protected volatile InputStream in;
protected FilterInputStream(InputStream in) {
this.in = in;
}
}
public class BufferedInputStream extends FilterInputStream {
public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
}
}
BufferedInputStream
将 InputStream
对象传入构造方法,并继承 FilterInputStream
类,实现了对 InputStream
方法的装饰。因此我们可以这样调用:
BufferedReader fileReader = new BufferedReader(new FileReader(""));
fileReader.readLine();
BufferedReader strReader = new BufferedReader(new StringReader(""));
strReader.readLine();
Spring 中的装饰器模式
Spring 中的 TransactionAwareCacheDecorator
类:
public class TransactionAwareCacheDecorator implements Cache {
private final Cache targetCache;
public TransactionAwareCacheDecorator(Cache targetCache) {
Assert.notNull(targetCache, "Target Cache must not be null");
this.targetCache = targetCache;
}
}
小结
装饰器模式和代理模式对比
从代理模式的 UML 类图和通用代码实现上看,代理模式与装饰器模式几乎一模一样。代理模式的 Subject 对应装饰器模式的 Component,代理模式的 RealSubject 对应装饰器模式的 ConcreteComponent,代理模式的 Proxy 对应装饰器模式的 Decorator 。
确实,从代码实现上看,代理模式的确与装饰器模式是一样的(其实装饰器模式就是代理模式的一个特殊应用),但是这两种设计模式所面向的功能扩展面是不一样的:
- 装饰器模式强调自身功能的扩展,Decorator 所做的就是增强 ConcreteComponent 的功能 (也有可能减弱功能),主体对象为 ConcreteComponent,核心是类功能的变化;
- 代理模式强调对代理过程的控制,Proxy 完全掌握对 RealSubject 的访问控制,因此,Proxy 可以决定对 RealSubject 进行功能扩展,功能缩减甚至功能散失(不调用 RealSubject 方法),主体对象为 Proxy 。
装饰器模式的优缺点
装饰器模式的优点:
- 装饰器是对继承体系的补充,比直接使用继承灵活,可以在不改变原有对象的情况下动态地给一个对象扩展功能,即插即用。
- 通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果。
- 装饰器完全遵守开闭原则。
缺点:
- 装饰器模式会派生出更多的代码,更多的类,增加程序复杂性。
- 动态装饰时,多层装饰时会更复杂。
文章评论