设计模式详解——装饰者模式

前言

在程序开发设计中,有一个特别重要的原则是,类应该对扩展开放,对修改关闭,虽然这一原则听起来很矛盾,但是在一些比较优秀的设计模式中,是完全可以达成这一原则的,比如装饰者模式,它就是这一原则的最佳实践,下面我们来看下它的基本原理和用法,希望能通过这篇内容,引发给位小伙伴对于设计模式的思考。

装饰者模式

装饰者模式动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性地替代方案。

要点

  • 继承属于扩展形式之一,但不见得是达到弹性设计的最佳方式;
  • 在我们的设计中,应该允许行为可以被扩展,而无须修改现有代码
  • 组合和委托可用于在运行时动态地加上新的行为
  • 除了继承,装饰者模式也可以让我们扩展行为
  • 装饰者模式意味着一群装饰者类,这些类用来包装具体组件
  • 装饰者类反映出被装饰组件地类型(他们具有相同的类型,都经过接口或继承实现)
  • 装饰者可以在被装饰者地行为前面或者后面加上自己的行为,甚至将被装饰者的行为整体覆盖,从而达到特定目的
  • 装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型
  • 装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得复杂。

示例代码

原始对象接口

基类接口,也是包装类最底层的内容(俄罗斯套娃里面第一个被套的那个),也可以理解为包装类中的最小单位,这里我们用形状这个抽象概念作为示例演示。

首先这个抽象接口有一个基本方法,就是draw绘制方法,凡是实现了形状这个接口的所有类都必须实现这个接口。

public interface Shape {
    /**
     * 绘制方法
     */
    void draw();
}
接口实现

这里是接口的实现,不过为了方便理解,各位小伙伴可以把接口的实现当作第一层包装类,这一层包装相当于直接确定了我们形状的基本属性,表明它是一个圆形。

public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("绘制圆形");
    }
}

同理,这里确定了我们的形状为矩形。

public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("绘制矩形");
    }
}
装饰对象抽象

这里相当于做一个通用的包装接口,后续的包装类只需要继承这个抽象类,然后再在其中加入新的行为或属性即可,而不需要重复封装。

public abstract class ShapeDecorator implements Shape {
    protected Shape decoratedShape;

    public ShapeDecorator(Shape decoratedShape){
        this.decoratedShape = decoratedShape;
    }

    public void draw(){
        decoratedShape.draw();
    }
}
装饰者实现

这里就是第一层包装(如果按我们上面的说法应该是第三层,因为我们传递的肯定是shape的实现,而不是抽象接口),这里继承了上面的抽象包装类。

public class RedShapeDecorator extends ShapeDecorator {

    public RedShapeDecorator(Shape decoratedShape) {
        super(decoratedShape);
    }

    @Override
    public void draw() {
        decoratedShape.draw();
        setRedBorder(decoratedShape);
    }

    private void setRedBorder(Shape decoratedShape){
        System.out.println("Border Color: Red");
    }
}

同时引入了一个新的方法setRedBorder,这个方法让我们的形状有了一个新的属性——红色。当然,我们还可以继续封装,我们可以在颜色的基础上给他封装一个尺寸:

public class SizeShapeDecorator extends RedShapeDecorator{

    public SizeShapeDecorator(Shape decoratedShape) {
        super(decoratedShape);
    }

    @Override
    public void draw() {
        super.draw();
        setSize(decoratedShape);
    }

    private void setSize(Shape decoratedShape) {
        System.out.println("size: 100");
    }
}
测试代码

这里我们分别创建一个圆形和矩形,指定不同的包装类,并调用他们的draw方法:

	@Test
    public void testDecorator() {
        Shape circle = new Circle();
        ShapeDecorator redCircle = new RedShapeDecorator(new Circle());
        ShapeDecorator redRectangle = new RedShapeDecorator(new Rectangle());
        ShapeDecorator sizeRedRectangle = new SizeShapeDecorator(new Rectangle());
        System.out.println("原始圆形");
        circle.draw();

        System.out.println("===========\n包装的红色圆形");
        redCircle.draw();

        System.out.println("===========\n包装的红色矩形");
        redRectangle.draw(); 
        
        System.out.println("===========\n包装的100红色矩形");
        sizeRedRectangle.draw();
    }

最后运行结果如下:

总结

从示例代码及运行结果我们可以看出来,虽然后续我们不断为原始的形状加入了新的属性,但是我们并没有改变原始代码,而是一层一层地添加不同的包装类,然后让它逐步有了更多的新属性。

这样的操作好处是,我们原有的形状拥有了新的属性,但这些后增加的属性和原始形状之间并没有任何强依赖关系,也没有破坏原因业务逻辑,保证了系统的可扩展性,同时耦合性还很低,这就是装饰者设计模式的魅力。

不知道各位小伙伴是否还记得我们前段时间分享的spring boot源码相关内容,spring boot的源码中就用到了装饰者模式,比如BeanFactory的相关内容,另外我们日常开发过程中用到的Tomcat有好多地方也用到了这种设计模式,最典型的是ServletRequestServletResponse

原文地址:https://www.cnblogs.com/caoleiCoding/p/15412914.html