在这之前我们先看修饰模式介绍
装饰者模式,动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更加有弹性的替代方案。
组合和继承的区别
继承。继承是给一个类添加行为的比较有效的途径。通过使用继承,可以使得子类在拥有自身方法的同时,还可以拥有父类的方法。但是使用继承是静态的,在编译的时候就已经决定了子类的行为,我们不便于控制增加行为的方式和时机。
组合。组合即将一个对象嵌入到另一个对象中,由另一个对象来决定是否引用该对象来扩展自己的行为。这是一种动态的方式,我们可以在应用程序中动态的控制。
与继承相比,组合关系的优势就在于不会破坏类的封装性,且具有较好的松耦合性,可以使系统更加容易维护。但是它的缺点就在于要创建比继承更多的对象。
装饰者模式的优缺点
优点
1、装饰者模式可以提供比继承更多的灵活性
2、可以通过一种动态的方式来扩展一个对象的功能,在运行时选择不同的装饰器,从而实现不同的行为。
3、通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
4、具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。
缺点
1、会产生很多的小对象,增加了系统的复杂性
2、这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。
装饰者的使用场景
1、在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
2、需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
以上内容来自网络
UML图(Astah/jude)
示例:
装饰者基类
1 package com.xinye.test.decoration;
2 /**
3 * 食物基类
4 *
6 */
7 public abstract class Food {
8
9 protected String desc;
10
11 public abstract String getDesc();
12 }
鸡肉
1 package com.xinye.test.decoration;
2 /**
3 * 鸡肉
4 *
6 */
7 public class Chicken extends Food {
8 public Chicken(){
9 desc = "鸡肉";
10 }
11 @Override
12 public String getDesc() {
13 return desc;
14 }
15
16 }
鸭肉
1 package com.xinye.test.decoration;
2 /**
3 * 鸭肉
4 *
6 */
7 public class Duck extends Food {
8 public Duck(){
9 desc = "鸭肉";
10 }
11 @Override
12 public String getDesc() {
13 return desc;
14 }
15
16 }
装饰者基类
1 package com.xinye.test.decoration;
2 /**
3 *
4 *
6 */
7 public abstract class FoodDecoration extends Food {
8
9 @Override
10 public abstract String getDesc();
11
12 }
蒸-装饰者
1 package com.xinye.test.decoration;
2 /**
3 * 蒸食物
4 *
6 */
7 public class SteamedFood extends FoodDecoration {
8
9 private Food food;
10
11 public SteamedFood(Food f){
12 this.food = f;
13 }
14
15 @Override
16 public String getDesc() {
17 return getDecoration() + food.getDesc();
18 }
19
20 private String getDecoration(){
21 return "蒸";
22 }
23 }
烤-装饰者
1 package com.xinye.test.decoration;
2 /**
3 * 烤食物
4 *
6 */
7 public class RoastFood extends FoodDecoration {
8
9 private Food food;
10
11 public RoastFood(Food f){
12 this.food = f;
13 }
14
15 @Override
16 public String getDesc() {
17 return getDecoration() + food.getDesc();
18 }
19
20 private String getDecoration(){
21 return "烤";
22 }
23 }
客户端
1 package com.xinye.test.decoration;
2 /**
3 * 客户端
4 *
6 */
7 public class Client {
8 public static void main(String[] args) {
9 // 测试单纯的食物
10 Food f1 = new Chicken();
11 System.out.println(f1.getDesc());
12
13 System.out.println("----------------------");
14 // 测试单重修饰的食物
15 RoastFood rf = new RoastFood(f1);
16 System.out.println(rf.getDesc());
17
18 System.out.println("----------------------");
19 // 测试多重修饰的食物
20 SteamedFood sf = new SteamedFood(rf);
21 System.out.println(sf.getDesc());
22 }
23 }
执行结果:
鸡肉
----------------------
烤鸡肉
----------------------
蒸烤鸡肉
laravel中的修饰者模式:
laravel框架中间件部分中使用的就是修饰者模式。
./vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php
比如一个处理一个http请求:
入口文件index.php
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
容器实例化App\Http\Kernel extends Illuminate\Foundation\Http\Kernel
class Kernel{
/**
* Handle an incoming HTTP request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function handle($request)
{
try {
$request->enableHttpMethodParameterOverride();
$response = $this->sendRequestThroughRouter($request);
} catch (Exception $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
} catch (Throwable $e) {
$this->reportException($e = new FatalThrowableError($e));
$response = $this->renderException($request, $e);
}
$this->app['events']->fire('kernel.handled', [$request, $response]);
return $response;
}
/**
* Send the given request through the middleware / router.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap();
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
}
实例Illuminate\Routing\Pipeline extends Illuminate\Pipeline\Pipeline的then()方法
class Pipeline{
/**
* Run the pipeline with a final destination callback.
*
* @param \Closure $destination
* @return mixed
*/
public function then(Closure $destination)
{
$firstSlice = $this->getInitialSlice($destination);
$pipes = array_reverse($this->pipes);
return call_user_func(
array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable
);
}
/**
* Get a Closure that represents a slice of the application onion.
*
* @return \Closure
*/
protected function getSlice()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
if ($pipe instanceof Closure) {
// If the pipe is an instance of a Closure, we will just call it directly but
// otherwise we'll resolve the pipes out of the container and call it with
// the appropriate method and arguments, returning the results back out.
return call_user_func($pipe, $passable, $stack);
} elseif (! is_object($pipe)) {
list($name, $parameters) = $this->parsePipeString($pipe);
// If the pipe is a string we will parse the string and resolve the class out
// of the dependency injection container. We can then build a callable and
// execute the pipe function giving in the parameters that are required.
$pipe = $this->container->make($name);
$parameters = array_merge([$passable, $stack], $parameters);
} else {
// If the pipe is already an object we'll just make a callable and pass it to
// the pipe as-is. There is no need to do any extra parsing and formatting
// since the object we're given was already a fully instantiated object.
$parameters = [$passable, $stack];
}
return call_user_func_array([$pipe, $this->method], $parameters);
};
};
}
/**
* Get the initial slice to begin the stack call.
*
* @param \Closure $destination
* @return \Closure
*/
protected function getInitialSlice(Closure $destination)
{
return function ($passable) use ($destination) {
return call_user_func($destination, $passable);
};
}
}
其中包含大量的闭包函数。其中array_reduce()其主要作用。
return call_user_func(
array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable
);
下面 我们简化该中间件实现过程。
<?php
interface Middleware
{
public static function handle($request='1块',Closure $next);
}
//请求开始 逐一穿过
class ranhaiqing implements Middleware
{
public static function handle($request,Closure $next)
{
echo __class__.":好烦啊,又借{$request}\n";
$next($request);
echo __class__.":就这么多钱了。\n";
}
}
class zhaodongran implements Middleware
{
public static function handle($request,Closure $next)
{
echo __class__.":哎呀我也{$request}钱,别着急我帮你想办法\n";
$next($request);
echo __class__.":这个钱来的不容易啊\n";
}
}
class pangxiaofei implements Middleware
{
public static function handle($request,Closure $next)
{
echo __class__.":我没有,我向旁边人先借{$request}\n";
$next($request);
}
}
class wangdongyang implements Middleware
{
public static function handle($request,Closure $next)
{
echo __class__.":那行吧,我从小额贷撸{$request}\n";
$next($request);
echo __class__.":%@!……*-#!\n";
}
}
等同于_getSlice方法/
function getSlice(){
return function ($stack, $pipe) {
return function ($request) use ($stack, $pipe) {
$slice = parentGetSlice();
return call_user_func($slice($stack, $pipe), $request);
};
};
}
function parentGetSlice()
{
return function ($stack, $pipe){
return function ($request) use ($stack, $pipe) {
return $pipe::handle($request,$stack);
};
};
}
///
function _getSlice($stack, $pipe)
{
return function ($request) use ($stack, $pipe) {
return $pipe::handle($request,$stack);
//return call_user_func_array([$pipe, 'handle'], [$request,$stack]);
};
}
function then($request)
{
$pipes = [
"ranhaiqing",
"zhaodongran",
"pangxiaofei",
"wangdongyang",
];
$firstSlice = function($request) {
echo "终于有{$request}了\n";
};
//数组数据反转
$pipes = array_reverse($pipes);
$go = array_reduce($pipes, getSlice(),$firstSlice);//var_dump($go);exit;
return $go($request);
// return call_user_func(
// array_reduce($pipes, getSlice(), $firstSlice), $request
// );
}
//借钱开始:
then('1000块');
假如有这样一个需求。“经理找组长借钱,然后组长没钱,找旁边的人借(zdr),然后zdr也是个穷b 找pxf借。pxf最后找wdy借。
最后wdy没办法了。只好从贷款100元。”
分析:我们从上帝视角来分析这个需求。当然了,到最后经理借到了这100块钱。这100块钱也经过了很多人的手。这100块钱实质上是出自贷款。但是经过了4个人的手,你也可以在数组$pipes中自定义顺序和人数。
运行结果:
ranhaiqing:好烦啊,又借1000块
zhaodongran:哎呀我也1000块钱,别着急我帮你想办法
pangxiaofei:我没有,我向旁边人先借1000块
wangdongyang:那行吧,我从小额贷撸1000块
终于有1000块了
wangdongyang:%@!……*-#!
zhaodongran:这个钱来的不容易啊
ranhaiqing:就这么多钱了。
图解pip管道: