前言

上期责任链模式文章里面给大家提到的学习设计模式的心得,本来是要在这期写给铁子们的,但是我思前想后,感觉时机尚未成熟。我准备在「 轻松搞懂设计模式 」系列结束后的总结性文章里面告诉大伙。

絮絮叨叨

不知道大家有没有这样的感觉,就是设计模式好像大多用在框架里面,自己的业务代码里面基本没有使用过设计模式。其实很多业务场景都可以使用设计模式来解决。想想都有点美滋滋呢 😍,起飞起飞~✈️

概念

优点

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

缺点

  • 装饰器模式会增加许多子类,过度使用会增加程序得复杂性。

为什么不使用继承来搞?

通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。

装饰者模式的目标

使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能。

装饰者模式使用场景

  • 当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
  • 当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰器模式却很好实现。
  • 当对象的功能要求可以动态地添加,也可以再动态地撤销时。

定义角色
目标对象:被装饰的原始对象;
装饰者抽象:抽象的装饰者;
具体装饰者:装饰者抽象的具体实现。

代码示例

订单类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
    private Double price;
    private Integer orderId;
    private String orderNo;
    private Double pay;
    private List<Integer> promotionTypeList;
}

活动的枚举类,用于记录活动类型

/**
 * 活动枚举
 */
public enum PromotionType {
    COUPON(1),
    VIP(2);
    private int type;

    PromotionType(final int type){
        this.type = type;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }
}

装饰订单的装饰者接口

/**
 * 订单装饰者接口
 */
public interface OrderDecorator {
    //抽象装饰方法
    Order decorator(Order order);
}

订单装饰者实现类--优惠券订单装饰者

/**
 * 优惠券装饰者
 */
public class CouponOrderDecorator implements OrderDecorator {
    @Override
    public Order decorator(Order order) {
        System.out.println("优惠券减去3元");
        order.setPay(order.getPrice() - 3);
        order.setPrice(order.getPrice() - 3);
        System.out.println("实付:" + order.getPay());
        return order;
    }
}

订单装饰者实现类--VIP订单装饰者

/**
 * vip装饰者
 */
public class VipOrderDecorator implements OrderDecorator {
    @Override
    public Order decorator(Order order) {
        System.out.println("vip减去1元");
        order.setPay(order.getPrice() - 1);
        order.setPrice(order.getPrice() - 1);
        System.out.println("实付:" + order.getPay());
        return order;
    }
}

下面就看看具体怎么使用吧

public class DecoratorClient {
    public static void main(String[] args) {
        //创建一个原始订单
        Order order = new Order(10d, 1, "0001", 10d, null);
        //创建装饰者子类对象
        OrderDecorator vipOrderDecorator = new VipOrderDecorator();
        OrderDecorator couponOrderDecorator = new CouponOrderDecorator();
        //使用vip和优惠券包装原订单对象,使其拥有vip和优惠券的优惠。
        //在某些情况下(根据具体需求来实现)还可以根据包装的顺序不同而获得不同的结果。
        Order vipCouponOrder = couponOrderDecorator.decorator(vipOrderDecorator.decorator(order));
    }
}

装饰者模式在开源代码中的使用

java.io中的应用

装饰者模式在 Java 语言中的最著名的应用莫过于 Java I/O 标准库的设计了。例如,InputStream 的子类 FilterInputStreamOutputStream 的子类 FilterOutputStreamReader 的子类 BufferedReader 以及 FilterReader,还有 Writer 的子类 BufferedWriterFilterWriter 以及 PrintWriter 等,它们都是抽象装饰类。

下面看一个例子,BufferedReader实现装饰者的方式是继承Reader类,然后将Reader类定义为自己的成员变量。这里的Reader即是装饰者抽象,又是目标对象的抽象,BufferedReader是具体装饰者,又是目标对象。

FilterReader的方式和BufferedReader的实现方式一样

为什么BufferedReader没有使用上面示例代码的实现方式呢?

这其实就是老生常谈的--设计模式是一种思想,实现方式有多种多样。BufferedReader类内部没有类似示例代码中的decorator()方法,所以它通过将目标对象定义为成员变量的方式来引入目标对象(而不是将目标对象定义形参的方式),从而达到将其他Reader实现类的功能装饰到自身的目的。

这里只是简单的说明了这种实现装饰者的用法,具体的内部源码大家可以自己去看一下,方法的内部最终还是调用了in.xxx方法。

public static void main(String[] args) throws IOException {
        Reader file = new FileReader("xxx路径");
        //FilterReader中使用FileReader进行装饰
        FilterReader filterReader = new PushbackReader(file);
        //BufferedReader中使用FilterReader进行装饰
        Reader bufferedReader = new BufferedReader(filterReader);
        //最终调用BufferedReader的read,close方法
        //就获得了FilterReader以及FileReader的功能
        bufferedReader.read();
        bufferedReader.close();
    }

shenyu网关中的应用

shenyu上下文装饰者接口,主要用于根据不同的接入类型来组装不同的上下文信息。

shenyugrpc上下文装饰者实现类,这种实现方式就和实例代码一样,有一个decorator()方法将目标对象ShenyuContext当做形参传入,然后在该方法内对ShenyuContext进行grpc类型的装饰。


shenyu也是一个比较nice的开源网关框架,是国人开源的新作品,有很高的学习价值和实用性。我在老东家的架构组当时就是引入了该网关,由我们架构组维护和二开。所以对这个网关框架内部实现比较熟悉,也会经常用它来举例子。

总结

还是老生常谈的设计模式是一种思想,我们要的是掌握理解思想,并将其转化为代码中的实现,应用到实际的工作中。兄弟们平时可以多拉几个开源框架的源码到本地,没事的时候就看看人家怎么写的代码,比如装饰者模式就可以全局搜索decorator来查找相关代码。

微信公众号<font color=#2941c0>「 袋鼠先生的客栈 」</font>,你们的三连,是我的饲料~~。点赞👍 关注❤️ 分享👥

03-05 14:50