发布网友 发布时间:2022-04-06 05:41
共2个回答
懂视网 时间:2022-04-06 10:02
拉面的故事
拉面馆里卖拉面,拉面分为小碗和大碗,小碗一份6元,大碗一份9元。另外如果加牛肉的话,则需加6元,加一个鸡蛋是1元,加大排是5元一份,加一块锅巴是1元。如果用传统的写法,设置不同价格的拉面,需要写8个类(拉面份量数*配菜数)。如果现在面馆新推一种份量——中碗,那么,就需要新增4个类。这样就会造成一个问题——类爆炸。
如果你看过我之前的文章https://www.php.cn/php-weizijiaocheng-457250.html,了解了桥接模式后,会觉得这个问题可以用桥接模式来解决。把它分为两个大类,面条和配菜。
下面我们用桥接模式来完成上述问题,代码如下:
interface INoodle { function cost (); function desc (); } class BigNoodle implements INoodle { private $cost = 9.0; private $dish = null; public function __construct(IDish $dish) { $this->dish = $dish; } public function cost() { return $this->cost + $this->dish->cost(); } public function desc() { return $this->dish->desc() . '大碗拉面'; } } class SmallNoodle implements INoodle { private $cost = 6.0; private $dish = null; public function __construct(IDish $dish) { $this->dish = $dish; } public function cost() { return $this->cost + $this->dish->cost(); } public function desc() { return $this->dish->desc() . '小碗拉面'; } } interface IDish { function cost (); function desc (); } class Beef implements IDish { public function cost () { return 6; } public function desc() { return '牛肉'; } } class Crust implements IDish { public function cost () { return 1; } public function desc() { return '锅巴'; } } class Egg implements IDish { public function cost () { return 1; } public function desc() { return '鸡蛋'; } }
装饰者模式
使用桥接模式确实解决了类爆炸问题,但你也知道,我们去吃面,可能有时候不要配菜,只要面,又或者我们需要多个配菜,比如,我要份大碗牛肉拉面,加3块锅巴以及2个鸡蛋。对于这种需求,使用桥接模式是完成不了的。想要解决这种问题,我们可以借助另一种结构型设计模式——装饰者模式。
装饰模式是一种结构型设计模式, 允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。
想要理解装饰者模式,可以想象一个玩偶——套娃
每套一个娃,就相当于添加了一个装饰的对象。在运行时,会运行最外层的装饰对象(取外层的娃),然后一层一层的运行。现在你可能不懂什么意思,看完后面的内容然后再来会看这句话或许就会明白。
我自己画了个uml类图,有点丑,大家将就点
代码实现
abstract class Noodles { abstract function cost (); abstract function desc (); } class BigNoodle extends Noodles { private $cost = 9.0; public function cost() { return $this->cost; } public function desc() { return '大碗拉面'; } } class SmallNoodle extends Noodles { private $cost = 6.0; public function cost() { return $this->cost; } public function desc() { return '小碗拉面'; } } abstract class NoodlesDecorator extends Noodles { } class Beef extends NoodlesDecorator { private $desc = '牛肉'; private $cost = 6.0; protected $noodles = null; public function __construct(Noodles $noodels) { $this->noodles = $noodels; } public function cost () { return $this->cost + $this->noodles->cost(); } public function desc () { return $this->desc . $this->noodles->desc(); } } // egg、curst类代码省略,除了属性值不一样基本和Beef一致
测试代码如下
$noodles = new BigNoodle(); $beefBigNoodles = new Beef($noodles); $eggBeffBigNoodles = new Egg($beefBigNoodles); echo $eggBeffBigNoodles->desc(); echo $eggBeffBigNoodles->cost() . '元';
结果输出:鸡蛋牛肉大碗拉面16元
总结
思考一个问题,为什么这里没有把拉面的份量作为装饰者对象?想想看,你会点一份既是大碗又是小碗的拉面吗?
装饰者模式特点
装饰者和被装饰者对象有相同的超类型
可以用一个或多个装饰者包装一个对象
对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。
热心网友 时间:2022-04-06 07:10
1.关于新职责:适配器也可以在转换时增加新的职责,但主要目的不在此。装饰者模式主要是给被装饰者增加新职责的。
2.关于原接口:适配器模式是用新接口来调用原接口,原接口对新系统是不可见或者说不可用的。装饰者模式原封不动的使用原接口,系统对装饰的对象也通过原接口来完成使用。(增加新接口的装饰者模式可以认为是其变种--“半透明”装饰者)
3.关于其包裹的对象:适配器是知道被适配者的详细情况的(就是那个类或那个接口)。装饰者只知道其接口是什么,至于其具体类型(是基类还是其他派生类)只有在运行期间才知道。