装饰模式 - Decorator Pattern
定义
动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。这样可以给某个对象而不是整个类添加一些功能。
设计的原则和思想
- 解耦的是装饰者和被装饰者。
- 不变部分是被装饰者,变化部分是装饰者。
- 核心思想是对对象自身功能进行增强。
一句话概括设计模式
通过组合的方式,增强对象自身的功能,并且支持多个装饰器嵌套使用。
经典装饰者模式
结构中包含的角色
- Component(抽象构件)
- ConcreteComponent(具体构件)
- Decorator(抽象装饰类)
- ConcreteDecorator(具体装饰类)
最小可表达代码
// 抽象构件类
abstract class Component
{
public abstract function display();
}
// 具体构件类
class ConcreteComponent extends Component
{
public function display()
{
echo ' 具体构件 ';
}
}
// 抽象装饰类
abstract class ComponentDecorator extends Component
{
protected $component;
public function __construct(Component $component)
{
$this->component = $component;
}
}
// 具体装饰类
class ConcreteDecorator extends ComponentDecorator
{
public function display()
{
echo ' 装饰前的行为 ';
$this->component->display();
echo ' 装饰后的行为 ';
}
}
$decorator = new ConcreteDecorator(new ConcreteComponent);
$decorator->display();
Laravel管道闭包方式的装饰者模式
结构中包含的角色
- MiddlewareManager (中间件管理类/修饰者管理类)
- Middleware (抽象中间件/抽象装饰者)
- ConcreteMiddleWare (具体中间件/具体装饰者)
- Controller (控制器/被装饰者)
- Request (请求类)
最小可表达代码
class MiddlewareManager
{
// 请求
protected $request;
// 中间件 middlewares
protected $middlewares = [];
// 设置请求
public function send($request)
{
$this->request = $request;
return $this;
}
// 设置中间件
public function through($middlewares)
{
$this->middlewares = is_array($middlewares) ? $middlewares : func_get_args();
return $this;
}
// 输入需要执行的控制器,并且开始装饰控制器
public function then(Closure $controllerClosure)
{
$middlewareClosure = array_reduce(
array_reverse($this->middlewares), $this->carry(), $this->prepareDestination($controllerClosure)
);
// 很多人不太熟悉 array_reduce 的用法,这里附上 foreach 的写法,方便理解。
// $middlewareClosure = $this->prepareDestination($controllerClosure);
// foreach (array_reverse($this->middlewares) as $middleware) {
// $middlewareClosure = function ($request) use ($middlewareClosure, $middleware) {
// return $middleware->handle($request, $middlewareClosure);
// };
// }
return $middlewareClosure($this->request);
}
// 设置请求作为参数传给控制器
protected function prepareDestination(Closure $controllerClosure)
{
return function ($request) use ($controllerClosure) {
return $controllerClosure($request);
};
}
// 执行多个中间件的方法
protected function carry()
{
return function ($controllerClosure, $middleware) {
return function ($request) use ($controllerClosure, $middleware) {
return $middleware->handle($request, $controllerClosure);
};
};
}
}
class Request{}
interface Middleware
{
public function handle(Request $request, Closure $controllerClosure);
}
class ConcreteMiddleWare implements Middleware
{
public function handle(Request $request, Closure $controllerClosure)
{
var_dump("前");
$response = $controllerClosure($request);
var_dump("后");
return $response;
}
}
class Controller
{
public function test(Request $request)
{
var_dump("执行控制器的方法");
return ['status' => 200];
}
}
$response = (new MiddlewareManager)
->send(new Request)
->through([new ConcreteMiddleWare])
->then(function ($request) {
return (new Controller)->test($request);
});
var_dump($response);
优点
- 你可以在运行时添加或删除对象的功能。
- 你可以用多个装饰封装对象来组合几种行为。
- 装饰类和被装饰类可以独立发展,不会相互耦合。
- 通过组合来替代继承,使用对象之间的关联关系取代类之间的继承关系。
缺点
- 在封装器栈中删除指定装饰器比较困难。
- 实现行为不受装饰栈顺序影响的装饰比较困难。
- 比继承更容易出错,排错也比较困难,代码较为繁琐。
- 多层装饰比较复杂。
何时使用
- 扩展一个类的功能。
- 动态增加功能。
- 当不能采用继承的方式(final)对系统进行扩展或者采用继承不利于系统扩展和维护(大量独立扩展)
实际应用场景
- Laravel的中间件。
- 汉堡包,加番茄酱,加牛肉,加鸡蛋。。。
- Java的IO流。