设计模式:装饰者模式

编程入门 行业动态 更新时间:2024-10-06 19:28:35

设计<a href=https://www.elefans.com/category/jswz/34/1771241.html style=模式:装饰者模式"/>

设计模式:装饰者模式

缘由

没什么特别的,之前看懂了,这次自己再复述一下。毕竟把别人讲懂了才是真的懂了。主要参考了head first 设计模式。

书中例子

面和具体的面

例子讲述的是在为星巴克咖啡的制作订单的情况,比如客人点了饮料,那么系统会自动算出价格(不知道是我没有体会到,还是这个例子不太合适,算出价格那么简单的事还需要用到类?,不过不影响我们思考装饰者模式)。不过似乎星巴克离普通的中国人还是太遥远了,我倒可以认为我们想象成中国面馆比较好。
这家中国面馆卖很多种类的面,有我喜欢的哨子面、牛肉面、杂酱面、板面等等面,每一个面都有价格,这是非常正常的。如果每一种面都有一个价格,那么这个订单生成系统好像还比较简单。因为继承是一种不错的选择。下图直接截取了head first 设计模式,所以里面是饮料。那么饮料就相当于我上面所述的面,而牛肉面就相当于一个饮料的一种,比如:Dark Roast。

加了配料的面

问题来了,面还可以加各类配料,比如你说老板多加一个鸡蛋,另一个说多加一个肉丸,多加一个火腿肠。难道我们为每一种加了配料的面都来实现一种类来计算价钱吗?如此一来,将会有非常多的类。比如加了鸡蛋的牛肉面、加了肉丸的牛肉面.....那么我们的类就继承就会成为下面这个样子。

可以看出我们要实现每一个子类就非常辛苦了,需要自己懂把加了丸子、鸡蛋的牛肉面,提前加一遍算好放在这个子类里使用。更可怕的是:如果丸子价格涨了,怎么办?我们又要把所有的有丸子的子类都加一遍吗?

将调料试着实例变量

这是一种可以解决的方案。在父类面里面有丸子、鸡蛋的布尔值,和具体价格,那么用户在选择添加了鸡蛋的时候,就将鸡蛋的布尔值改为true,然后再将父类的cost()方法实现为加上所有的配料的价格。那么子类在cost()方法里面用自己这道面的价格加上父类的cost(),就是配料的价格,那么就算出了总共需要多少钱了。




下图是伪代码,我们可以看出,子类DarkRoast的cost()方面里面调用了父类的cost()方法,而父类cost()方面,主要是判断这个订单有没有加这个某种配料,加了的话就加上这个配料的价格。


然而,这样做有以下三种情况会使我们很难处理:
  1. 如果出现新调料,我们需要改变超类。似乎这样很不好
  2. 某些饮料里面就不能加某些调料,然而我们并没有限制
  3. 如果某个客户想要双倍的调料,则无法处理。
这里书中: 提出了类应该对扩展开放,对修改关闭
因为
  • 对类修改也许会引入额外的bug,那么上面的设计也许会使得我们修改超类,这样非常不好。
  • 而如果我们有需要,可以通过对类进行扩展来完成。

引入装饰者模式

此时书中引入装饰者模式,其实我是知道看来了代码之后才知道到底是怎么回事。我的理解就是,所谓装饰者,其也是继承于基类(面)的一个实现类,这个实现类必须有有有一个成员变量:就是另一个实现类,也就是被包含的实现类,也就是我们装饰者所要装饰的对象。比如牛肉面就是一个实现类,他是被装饰的,可以被加鸡蛋这个实现类来修饰,加鸡蛋这个实现类的成员变量可以引用牛肉面,那么就组成了加鸡蛋的牛肉面,如果其成员变量引用了哨子面,那么就成了加鸡蛋的哨子面。而且,这个加鸡蛋的哨子面还可以被加丸子的实现类来装饰,那么就是成了加丸子 加鸡蛋的牛肉面。当然,也可以加两个鸡蛋。
所以,我们要把实现类分为两类,一种实现类时调料,其有一个成员变量就是基类的一个对象。另一个实现类就是面的种类:牛肉面、哨子面。
我在书上的图,作了一些额外的说明。如下:

另外书上也给了饮料的图,更为简单和清晰些:



代码

实际上我还是觉得代码描述的更为清楚:

public class TestMain {public static void main(String[] args) {//普通牛肉面Noodle beefNoodle = new BeefNoodle();System.out.println(beefNoodle.getDescription() + ":" + beefNoodle.cost());//普通猪肉面Noodle porkNoodle = new PorkNoodle();System.out.println(porkNoodle.getDescription() + ":" + porkNoodle.cost());//加了鸡蛋的牛肉面Noodle beefNoodle2 = new BeefNoodle();beefNoodle2 = new EggCondiment(beefNoodle2);System.out.println(beefNoodle2.getDescription() + ":" + beefNoodle2.cost());//加了鸡蛋和丸子的猪肉面Noodle porkNoodle2 = new PorkNoodle();porkNoodle2 = new EggCondiment(porkNoodle2);porkNoodle2 = new MeatballCondiment(porkNoodle2);System.out.println(porkNoodle2.getDescription() + ":" + porkNoodle2.cost());}
}

/*** 这是一个面的基类。所以面的种类的实现类和调料的装饰类都必须继承这个类。* @author zy**/
public abstract class Noodle {String description = "unknown noodle";public String getDescription(){return description;}public abstract double cost();}

public class BeefNoodle extends Noodle {public BeefNoodle() {// TODO Auto-generated constructor stubdescription = "牛肉面";}@Overridepublic double cost() {return 10;//表示这个碗牛肉面10元,当然可以做的更专业一些,用个字段来设置。}}

public class PorkNoodle extends Noodle {public PorkNoodle() {// TODO Auto-generated constructor stubdescription = "猪肉面";}@Overridepublic double cost() {// TODO Auto-generated method stubreturn 7;}}

public abstract class CondimenDecorator extends Noodle {/*** 用于装饰的实现类必须有一个成员变量就是要装饰的对象。在这里就是面* 使用Noodle这个基类的原因除了一般的表示种类的面* 用于装饰的实现类,还可以再次被装饰* 有点像加鸡蛋、加丸子的牛肉面* 被装饰了两次* * 此外,书上讲这个成员变量写在了每一个实现类里面,难道不能写在这个父类里面么?* 试试看。*/Noodle noodle;/*** 所以调料必须实现这个方法,这样才能知道这个面的具体描述* 比如 加鸡蛋的牛肉面*/public abstract String getDescription();
}

public class EggCondiment extends CondimenDecorator {public EggCondiment(Noodle noodle) {// TODO Auto-generated constructor stubthis.noodle = noodle;}@Overridepublic String getDescription() {// TODO Auto-generated method stubreturn noodle.getDescription() + "加鸡蛋";}@Overridepublic double cost() {// TODO Auto-generated method stubreturn noodle.cost() + 1;}}

public class MeatballCondiment extends CondimenDecorator {public MeatballCondiment(Noodle noodle) {// TODO Auto-generated constructor stubthis.noodle = noodle;}@Overridepublic String getDescription() {// TODO Auto-generated method stubreturn noodle.getDescription() + "加肉丸";}@Overridepublic double cost() {// TODO Auto-generated method stubreturn noodle.cost() + 2;}}



运行结果

牛肉面:10.0
猪肉面:7.0
牛肉面加鸡蛋:11.0
猪肉面加鸡蛋加肉丸:10.0

java的IO类实现了装饰者模式

如下图所示:


其中我们可以看出FilterInputStream专门作为装饰者。其实现类LineNumberInputStream就可以得到InputStream流中的行数。
书中还举出了简单的例子,自己写了一个FilterInputStream的实现类。也就是装饰类。有新兴趣的看看书吧

总结

比较有趣,不过这些设计模式都需要深刻的体会。懂了其含义只是刚刚开始吧。对了,定义很难理解。所以,一直没说。

源代码

没有上传网盘。代码比较简单。

更多推荐

设计模式:装饰者模式

本文发布于:2024-02-28 08:02:10,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1769113.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:模式

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!