Skip to content

装饰器模式

总述

装饰器模式,Decorator Pattern,用于动态地为某个对象添加属性,在不改变原始对象的结构的前提下。

  • 被修饰对象
  • 装饰器

常见例子 —— 普通咖啡,可以选择加糖、或者加奶(这里糖和奶就可以是装饰器)。某天遇到了一个神奇的用户要撒胡椒,就可以把胡椒封装成一个装饰器,这样就可以不用改变普通咖啡这个对象而实现动态增添属性。

实践

下面提供一个 C# 的实现一个可以往普通的形状上添加修饰的效果

首先创建一个抽象的接口,做一个形状的 “ 契约 ”

C#
public interface IShape
{
	public void Draw();
}

接着定义两个具体的形状,矩形和圆形(这边随便写的

C#
public class Rectangle : IShape
{
	private int _edges = 4;
	public string Name = "Rectangle";
	public void Draw()
	{
		Console.WriteLine(Name, _edges);
	}
}

public class Circle : IShape
{
	public void Draw()
	{
		Console.WriteLine("Circle, Perfect!");
	}
}

接着定义装饰器的抽象

C#
public abstract class DecoratorBase(IShape shape) : IShape
{
	// 需要装饰的形状, 由构造函数注入
	protected IShape _shape => shape;
	public virtual void Draw()
	{
		_shape.Draw();
	}
}

然后实现两个具体的装饰器

C#
public class StrokeDecorator(IShape shape) : DecoratorBase(shape)
{
	public float strokeWidth = 1.0f;
	public override void Draw()
	{
		base.Draw();
		Console.WriteLine("With Stroke {0}", strokeWidth);
	}
}

public class DropShadowDecorator(IShape shape) : DecoratorBase(shape)
{
	public float distance = 1.0f;
	[Range(0, 1)] public float transparent = 0.5f;
	public override void Draw()
	{
		base.Draw();
		Console.WriteLine($"With DropShadow: Distance {distance}, Transparent {transparent}");
	}
}

调用 ——

C#
public class DecoratorTest : ITest
{
    public void RunTest()
    {
        // 一个普通的矩形
        Console.WriteLine("Rectangle");
        IShape rectangle = new Rectangle();
        rectangle.Draw();
  
        Console.WriteLine("recWithStroke");
        IShape recWithStroke = new StrokeDecorator(rectangle);
        recWithStroke.Draw();

        Console.WriteLine("recWithStrokeAndDropShadow:");
        IShape recWithStrokeAndDropShadow = new DropShadowDecorator(recWithStroke);
        recWithStrokeAndDropShadow.Draw();
    }
}

首先创建了一个普通的矩形,然后先加上一个描边的特效,最后再加上一个投影的特效,最后就有了一个有描边和投影的矩形,非常的好使。一个非常常见的AE背景💦

一些思考

为什么装饰器也要继承被修饰对象的接口?——

  • 业务层只需要面向被修饰对象的接口进行编程,不需要额外的成本,即无需改变调用方式
  • 装饰链可任意层级叠加
  • 符合里氏替换原则

写在最后

优点缺点
符合开闭原则,扩展功能无需修改现有代码装饰链过长时,会增加调试和维护难度
可以动态组合多个职责,避免子类爆炸每增加一个装饰器就需要写一个类,类数量增加
装饰器和被装饰者对客户端透明多层装饰可能造成性能损耗

Released under the MIT License.