设计模式详解——装饰者模式
前言
在程序开发设计中,有一个特别重要的原则是,类应该对扩展开放,对修改关闭,虽然这一原则听起来很矛盾,但是在一些比较优秀的设计模式中,是完全可以达成这一原则的,比如装饰者模式,它就是这一原则的最佳实践,下面我们来看下它的基本原理和用法,希望能通过这篇内容,引发给位小伙伴对于设计模式的思考。
装饰者模式
装饰者模式动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
要点
继承属于扩展形式之一,但不见得是达到弹性设计的最佳方式; 在我们的设计中,应该允许行为可以被扩展,而无须修改现有代码 组合和委托可用于在运行时动态地加上新的行为 除了继承,装饰者模式也可以让我们扩展行为 装饰者模式意味着一群装饰者类,这些类用来包装具体组件 装饰者类反映出被装饰组件的类型(他们具有相同的类型,都经过接口或继承实现) 装饰者可以在被装饰者的行为前面或者后面加上自己的行为,甚至将被装饰者的行为整体覆盖,从而达到特定目的 装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型 装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得复杂。
示例代码
原始对象接口
基类接口,也是包装类最底层的内容(俄罗斯套娃里面第一个被套的那个),也可以理解为包装类中的最小单位,这里我们用形状这个抽象概念作为示例演示。
首先这个抽象接口有一个基本方法,就是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
有好多地方也用到了这种设计模式,最典型的是ServletRequest
和ServletResponse
。