装饰模式 (Decorator Pattern)

装饰模式可以实现动态的为对象添加功能,是从一个对象外部来给对象添加功能。一般有两种方式能够实现给一个类或对象增长行为:html

  • 继承机制,使用继承机制是给现有类添加功能的一种有效途径,经过继承一个现有类可使得子类在拥有自身方法的同时还拥有父类的方法。可是这种方法是静态的,用户不能控制增长行为的方式和时机。
  • 组合机制,即将一个类的对象嵌入另外一个对象中,由另外一个对象来决定是否调用嵌入对象的行为以便扩展本身的行为,咱们称这个嵌入的对象为装饰器(Decorator)

显然,为了扩展对象功能频繁修改父类或者派生子类这种方式并不可取。在面向对象的设计中,咱们应该尽可能使用对象组合,而不是对象继承来扩展和复用功能。装饰器模式就是基于对象组合的方式,能够很灵活的给对象添加所须要的功能。装饰器模式的本质就是动态组合。动态是手段,组合才是目的。总之,装饰模式是经过把复杂的功能简单化,分散化,而后在运行期间,根据须要来动态组合的这样一个模式。git

装饰模式定义

装饰模式(Decorator Pattern) :动态地给一个对象增长一些额外的职责(Responsibility),就增长对象功能来讲,装饰模式比生成子类实现更为灵活。其别名也能够称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不一样的场合。根据翻译的不一样,装饰模式也有人称之为“油漆工模式”,它是一种对象结构型模式。github

装饰模式的优势

  • 装饰模式与继承关系的目的都是要扩展对象的功能,可是装饰模式能够提供比继承更多的灵活性。
  • 能够经过一种动态的方式来扩展一个对象的功能,经过配置文件能够在运行时选择不一样的装饰器,从而实现不一样的行为。
  • 经过使用不一样的具体装饰类以及这些装饰类的排列组合,能够创造出不少不一样行为的组合。可使用多个具体装饰类来装饰同一对象,获得功能更为强大的对象。

模式结构和说明

装饰器模式UML

  • 聚合关系用一条带空心菱形箭头的直线表示,上图表示Component聚合到Decorator上,或者说Decorator由Component组成。
  • 继承关系用一条带空心箭头的直接表示
  • 看懂UML类图请看这个文档

Component:组件对象的接口,能够给这些对象动态的添加职责;bash

ConcreteComponent:具体的组件对象,实现了组件接口。该对象一般就是被装饰器装饰的原始对象,能够给这个对象添加职责;app

Decorator:全部装饰器的父类,须要定义一个与Component接口一致的接口(主要是为了实现装饰器功能的复用,即具体的装饰器A能够装饰另一个具体的装饰器B,由于装饰器类也是一个Component),并持有一个Component对象,该对象其实就是被装饰的对象。若是不继承Component接口类,则只能为某个组件添加单一的功能,即装饰器对象不能再装饰其余的装饰器对象。学习

ConcreteDecorator:具体的装饰器类,实现具体要向被装饰对象添加的功能。用来装饰具体的组件对象或者另一个具体的装饰器对象。ui

装饰器的示例代码

1.Component抽象类, 能够给这些对象动态的添加职责this

abstract class Component
{
	abstract public function operation();
}
复制代码

2.Component的实现类spa

class ConcreteComponent extends Component
{
	public function operation()
	{
		echo __CLASS__ .  '|' . __METHOD__ . "\r\n";
	}
}
复制代码

3.装饰器的抽象类,维持一个指向组件对象的接口对象, 并定义一个与组件接口一致的接口翻译

abstract class Decorator extends Component
{
	/**
	 * 持有Component的对象
	 */
	protected $component;

	/**
	 * 构造方法传入
	 */
	public function __construct(Component $component)
	{
		$this->component = $component;
	}

	abstract public function operation();
}
复制代码

4.装饰器的具体实现类,向组件对象添加职责,beforeOperation(),afterOperation()为先后添加的职责。

class ConcreteDecoratorA extends Decorator
{
	//在调用父类的operation方法的前置操做
	public function beforeOperation()
	{
		echo __CLASS__ . '|' . __METHOD__ . "\r\n";
	}

	//在调用父类的operation方法的后置操做
	public function afterOperation()
	{
		echo __CLASS__ . '|' . __METHOD__ . "\r\n";
	}

	public function operation()
	{
		$this->beforeOperation();
		$this->component->operation();//这里能够选择性的调用父类的方法,若是不调用则至关于彻底改写了方法,实现了新的功能
		$this->afterOperation();
	}
}

class ConcreteDecoratorB extends Decorator
{
	//在调用父类的operation方法的前置操做
	public function beforeOperation()
	{
		echo __CLASS__ . '|' . __METHOD__ . "\r\n";
	}

	//在调用父类的operation方法的后置操做
	public function afterOperation()
	{
		echo __CLASS__ . '|' . __METHOD__ . "\r\n";
	}

	public function operation()
	{
		$this->beforeOperation();
		$this->component->operation();//这里能够选择性的调用父类的方法,若是不调用则至关于彻底改写了方法,实现了新的功能
		$this->afterOperation();
	}
}
复制代码

5.客户端使用装饰器

class Client
{
	public function main()
	{
		$component = new ConcreteComponent();
		$decoratorA = new ConcreteDecoratorA($component);
		$decoratorB = new ConcreteDecoratorB($decoratorA);
		$decoratorB->operation();
	}
}

$client = new Client();
$client->main();
复制代码

6.运行结果

oncreteDecoratorB|ConcreteDecoratorB::beforeOperation
ConcreteDecoratorA|ConcreteDecoratorA::beforeOperation
ConcreteComponent|ConcreteComponent::operation
ConcreteDecoratorA|ConcreteDecoratorA::afterOperation
ConcreteDecoratorB|ConcreteDecoratorB::afterOperation
复制代码

装饰模式须要注意的问题

  • 一个装饰类的接口必须与被装饰类的接口保持相同,对于客户端来讲不管是装饰以前的对象仍是装饰以后的对象均可以一致对待。
  • 尽可能保持具体组件类ConcreteComponent的轻量,不要把主逻辑以外的辅助逻辑和状态放在具体组件类中,能够经过装饰类对其进行扩展。 若是只有一个具体组件类而没有抽象组件类,那么抽象装饰类能够做为具体组件类的直接子类。

适用环境

  • 须要在不影响组件对象的状况下,以动态、透明的方式给对象添加职责。
  • 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时能够考虑使用装饰类。

本文已经收录在系列文章Laravel源码学习里,欢迎访问阅读。

相关文章
相关标签/搜索