结构型模式-门面模式和装饰器模式

2020-03-07 1847点热度 0人点赞 0条评论
设计模式系列文章: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.");
        }
    }
}

依赖关系如下:

facade_buy_product

我们日常使用的框架、工具中也有很多基于门面模式的包装,如

  • Spring-JDBC:JdbcUtils 类中的 closeConnectioncloseStatementcloseResultSet 等方法
  • MyBatis:Configuration 类中的 newMetaObjectnewStatementHandlernewExecutor 等方法
  • Tomcat:RequestFacade 类,ResponseFacadeStandardSessionFacade
  • Controller 层对 Service 层的逻辑包装

门面模式与代理模式,单例模式

  • 门面模式是一种特殊的,没有代码增强的静态代理模式。
  • 门面模式的重点在于横向的逻辑封装,代理模式重点在于对原有对象的逻辑增强。
  • 在一些场景下,我们通常会将门面模式实现为单例模式,如工具类。

小结

门面模式的优点:

  1. 简化了调用过程,无需深入了解子系统,减小对子系统带来的风险
  2. 减少系统依赖,降低耦合
  3. 更好地划分逻辑层次,提高了系统安全性
  4. 遵循迪米特法则,即最少知道原则

缺点:

  1. 当子系统进行扩展或修改时,需要修改门面接口代码,可能带来未知风险
  2. 不符合开不原则
  3. 过于复杂的门面模式违背了单一职责原则(不强制实现接口,也不是面向接口编程)

装饰器模式

装饰器模式可以透明的动态的扩展对象的功能

装饰器模式 (Decorator Pattern) 也成包装模式 (Wrapper Pattern),核心是在不改变原有对象的基础上,将功能附加到对象上,提供了比继承更有弹性的扩展原有对象功能的方案。

其实现原理为:

  1. 让装饰器实现被包装类 (Concrete Component) 相同的接口 (Component),使得装饰器与被扩展类类型一致。
  2. 在装饰器类的构造函数中传入该接口 (Component) 对象,然后就可以在接口定义的方法中,在被包装类的对象的现有功能上添加新功能了。
  3. 由于装饰器与被包装类属于同一类型 (均实现 Component 接口),且构造函数的参数为其实现接口类 (Component),因此装饰器模式具备嵌套扩展功能。

使用场景:

  1. 用于扩展一个类的功能或给一个类添加附加职责。
  2. 动态的给一个对象添加功能,这些功能也可以再动态撤销。

应用场景

举个例子,我们使用 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 相关的类,如 BufferedReaderInputStreamOutputStream

InputStream 的类结构图:

inputstream_decorator_hierarchy

我们可以看出基于继承体系的装饰器模式在 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);
    }
}

BufferedInputStreamInputStream 对象传入构造方法,并继承 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 。

装饰器模式的优缺点

装饰器模式的优点:

  1. 装饰器是对继承体系的补充,比直接使用继承灵活,可以在不改变原有对象的情况下动态地给一个对象扩展功能,即插即用。
  2. 通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果。
  3. 装饰器完全遵守开闭原则。

缺点:

  1. 装饰器模式会派生出更多的代码,更多的类,增加程序复杂性。
  2. 动态装饰时,多层装饰时会更复杂。

SilverLining

也可能是只程序猿

文章评论