1 / 39

The Decorator Pattern (装饰模式)

The Decorator Pattern (装饰模式). 问题 …. 有这么一家咖啡连锁店,可以这样来形容其发展速度 : 如果今天在你吃午餐的小店边上有一家,明天在它的对面就会看到另一家。 由于生意如此之好,他们便急于升级它的点单系统以满足需要 开始时他们设计的类图如下:. 关于咖啡的知识: http://dilutesmalt.shineblog.com/user1/6507/archives/2005/158365.shtml. Beverage. description. getDescription() Cost() //Other methods.

adler
Télécharger la présentation

The Decorator Pattern (装饰模式)

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. The DecoratorPattern(装饰模式)

  2. 问题… • 有这么一家咖啡连锁店,可以这样来形容其发展速度:如果今天在你吃午餐的小店边上有一家,明天在它的对面就会看到另一家。 • 由于生意如此之好,他们便急于升级它的点单系统以满足需要 • 开始时他们设计的类图如下: 关于咖啡的知识:http://dilutesmalt.shineblog.com/user1/6507/archives/2005/158365.shtml

  3. Beverage description getDescription() Cost() //Other methods HouseBlend DarkRoast Decaf Espresso Cost() Cost() Cost() Cost() 抽象类,被该店的所有饮料类继承。实例变量description在子类中保存对相应饮料的描述。 初始类图 cost()方法是一个抽象方法。需要在子类中实现。 每个子类实现cost()方法,以计算它们的价格。

  4. 问题… • 除了咖啡,客人还可以选择一些调味品。每种调味品都要收取一些费用,所以这些也应该包含在点单系统之内。他们想通过每个类的cost()方法来实现。 • 结果是。。。

  5. Beverage description getDescription() Cost() //Other methods HouseBlend Espresso DarkRoast HouseBlend HouseBlend Espresso HouseBlend HouseBlend Decaf Decaf Espresso Decaf Espresso Decaf Espresso Decaf HouseBlend HouseBlendWithSteamed MilkandMocha Cost() Cost() Cost() Cost() Cost() Cost() Cost() Cost() Cost() Cost() Cost() Cost() Cost() Cost() Cost() Cost() Cost() Cost() 类爆炸!! 每个cost()方法计算出咖啡和调味品的价格。

  6. 这显然是一个恶梦 试设想: • 如果咖啡的价格发生变化 • 如果新出了一种调味品 对系统开发人员来说意味着什么?

  7. 怎么办? • 一种想法是,用实例变量和继承机制来解决各种调味品的问题

  8. Beverage description milk soy mocha whip getDescription() cost() hasMilk() setMilk() hasSoy() setSoy() … //Other methods 新设计方案中基类的类图 对应每种调味品的布尔型变量 现在,cost()方法在基类中将被实现,用以计算与特定饮料的调料价格,同时,该方法在子类中将被重写,并被调用以计算基本饮料价和调味品价的总价。 设置各种调味品实例变量的布尔值

  9. Beverage description milk soy mocha whip HouseBlend DarkRoast Decaf Espresso Cost() Cost() Cost() Cost() getDescription() cost() hasMilk() setMilk() hasSoy() setSoy() … //Other methods 新设计方案的类图 计算调料价格,该方法在子类中将被重写,扩展为计算包括基本饮料价格和调味品价格的饮料总价。 每个子类实现cost()方法,以计算它们的价格。

  10. 基类的cost()代码应该是怎样的? Public class Beverage { ... public double cost() { } ... }

  11. 子类的cost()代码应该是怎样的? Public class DarkRoast extends Beverage { public DarkRoast() { description=“优质DarkRoast咖啡”; } public double cost() { } ... }

  12. 这一设计方案只有五个类 • 该方案有什么不足? • 如果调味品价格变化怎么办? • 如果增加新的调料怎么办? • 如果一个顾客点了双份的某种调料怎么办?

  13. 面向对象设计的开-闭原则 • 类应该对扩展开放,对修改封闭。 • 目的是在不需修改已有代码的情况下方便的扩展类的功能。按照这一原则设计的系统具有以下优点: • 具有一定的适应性和灵活性。 • 具有一定的稳定性和延续性。

  14. 讨论 • “对扩展开放,对修改封闭。”这听起来自相矛盾啊,怎么可能? • 能否使设计的每一个部分都满足这一设计原则? • 我怎么知道那一部分应该遵循这一设计原则?

  15. 尽管看上去有些自相矛盾,但在设计中实现这一原则是可能的。观察者模式就遵循了这一原则。本讲的装饰模式也遵循这一原则。前面提出的咖啡店问题就可以用装饰模式很好的来解决。尽管看上去有些自相矛盾,但在设计中实现这一原则是可能的。观察者模式就遵循了这一原则。本讲的装饰模式也遵循这一原则。前面提出的咖啡店问题就可以用装饰模式很好的来解决。 • 在设计时应该认真选择需要扩展的部分。对设计的每一个部分都应用这一原则是不必要的,不仅费力,而且可能导致复杂难懂的代码。

  16. 装饰模式实战 • 我们已经看到用继承机制解决我们的咖啡店问题不太理想:或者会导致“类爆炸”,或者是导致基类包含一些子类中并不需要的成分。我们可以换一种做法:从一种基本饮料开始,在运行时用各种调味品对该饮料进行“装饰”。例如,如果一个顾客点了Darkroast咖啡加摩卡和蛋奶,那么就可以: • 生成一个Darkroast咖啡对象 • 用一个摩卡对象装饰它 • 用一个蛋奶对象装饰它 • 调用装饰后的对象的cost()方法,依靠委派来计算含调味品的咖啡价钱

  17. 如何装饰?怎样委派? • 我们将装饰对象想象成一个包装。

  18. 首先我们生成一个DarkRoast对象 DarkRoast Cost() DarkRoast继承了Beverage,拥有一个计算饮料价格的方法cost()。

  19. Mocha Cost() 然后 顾客想要摩卡,所以我们创建一个Mocha对象,并用它包装DarkRoast. DarkRoast Cost() Mocha对象是装饰者,他与被它装饰的对象DarkRoast具有相同的类型(是Beverage的子类),也有一个cost()方法。

  20. DarkRoast Cost() Mocha Cost() 再然后 顾客还想要蛋奶,所以我们创建一个Whip对象,并用它包装Mocha. Whip Cost() whip对象是装饰者,他与被它装饰的对象DarkRoast具有相同的类型,也有一个cost()方法。

  21. DarkRoast Cost() Mocha Cost() 5.00 5.00+0.50 5.50+1.00 现在,要计算饮料的价格 Whip Cost() 6.50 调用最外层的装饰者whip的cost(),whip再将计算任务委派给被它包装的对象,得到一个价格后,再加上whip自己的价格...

  22. 问题 • 如果一个顾客点了一份DarkRoast加两份mocha和一份soy,其装饰对象图和价格计算过程有什么样的呢?

  23. 要点 • 装饰者与被装饰者具有相同的类型 • 可以用多个装饰者装饰一个对象 • 由于装饰者与被装饰者具有相同的类型,我们可以用装饰后的对象代替原来的对象。 • 装饰者在委派他装饰的对象作某种处理时,可以添加上自己的行为(功能扩展)(在委派之前或/和之后)。 • 对象可以在任何时候被装饰,因此我们能在运行时动态的装饰对象。

  24. 装饰模式的定义 • 装饰模式可以动态的给一个对象附加一些功能,对于扩展功能来说,装饰模式(合成)比生成子类的方式(继承)更加灵活。

  25. Component methodA() methodB() //Other methods ConcreteDecoratorA component Component wrappedObj methodA() methodB() //other methods ConcreteComponent Decorator methodA() methodB() //Other methods methodA() methodB() //Other methods ConcreteDecoratorB Component wrappedObj Object newState methodA() methodB() //other methods 装饰模式的类图结构

  26. Beverage Mocha Whip Beverage beverage Beverage beverage description getDescription() Cost() getDescription() Cost() //Other methods getDescription() Cost() Decaf HouseBlend Espresso CondimentDecorator DarkRoast Cost() Cost() getDescription() Cost() Cost() 咖啡店的类图 具体的咖啡品种 调味品装饰者,不仅实现cost(),还要实现getDescription()

  27. 咖啡店源代码Beverage.java public abstract class Beverage { String description = "Unknown Beverage"; public String getDescription() { return description; } public abstract double cost(); }

  28. CondimentDecorator.java public abstract class CondimentDecorator extends Beverage { public abstract String getDescription(); }

  29. DarkRoast.java public class DarkRoast extends Beverage { public DarkRoast() { description = "Dark Roast Coffee"; } public double cost() { return 0.99; } }

  30. 请编写另外三种咖啡类的代码 • Espresso • HouseBlend • Decaf

  31. Mocha.java(具体装饰者:调味品Mocha) 实例化的调味品Mocha包装一种饮料:方法是申明一个Beverage实例变量,通过构造方法对该变量赋值,引用被包装的饮料。 public class Mocha extendsCondimentDecorator { Beverage beverage; public Mocha(Beverage beverage) { this.beverage = beverage; } public String getDescription() { return beverage.getDescription() + ", Mocha"; } public double cost() { return 0.20 + beverage.cost(); } } 我们希望不仅描述咖啡本身,还要包括调味品,所以先委派被包装的对象获取它的描述,再加上调味品的描述。 计算价格时,先委派被包装的对象计算其价格,再加上调味品的价格。

  32. 请编写其他调味品类的代码 • Whip • Milk • Soy

  33. 咖啡店营业:StarbuzzCoffee.java • public class StarbuzzCoffee { public static void main(String args[]) { Beverage beverage = new Espresso(); System.out.println(beverage.getDescription() + " $" + beverage.cost()); Beverage beverage2 = new DarkRoast(); beverage2 = new Mocha(beverage2); beverage2 = new Mocha(beverage2); beverage2 = new Whip(beverage2); System.out.println(beverage2.getDescription() + " $" + beverage2.cost()); Beverage beverage3 = new HouseBlend(); beverage3 = new Soy(beverage3); beverage3 = new Mocha(beverage3); beverage3 = new Whip(beverage3); System.out.println(beverage3.getDescription() + " $" + beverage3.cost()); } }

  34. 装饰模式实例 ---Java I/O • Java.io包中有大量的类,它们其实是按照装饰模式设计的。

  35. LineNumberInputStream BufferedInputStream FileInputStream 装饰模式实例 ---Java I/O • 下面是使用装饰者来增强从文件读入数据的功能的一个例子。 FileInputStream是Component。 BufferedInputStream是一个具体的装饰者,他从两个方面增强输入:提供缓冲以提高性能;新增一种方法readline()实现按行读入数据。 LineNumberInputStream也是一个具体的装饰者,它能在读入数据时计算读入的行数。

  36. Java.io中的装饰模式 抽象部件 具体部件,还有其他类此处没有列出。 InputStream 抽象的装饰者 FileInputStream StringBufferInputStream ByteArrayInputStream FilterInputStream PushBackInputStream BufferedInputStream DataInputStream LineNumberInputStream 有了这一讲所学的知识,你应该能够更好的使用java的输入流吧! 具体装饰者类

  37. 编写自己的java I/O装饰类 • 我们学了装饰模式,也看过了前面的java I/O类图,现在应该能够自己编写java I/O装饰类吧? • 试试看,编写一个装饰类,将输入的大写字符转换成小写字符。 • 例如:输入 I know the Decorator Pattern,装饰类会把它转换成:i know the decorator pattern

  38. 提示 • 我们将该装饰类取名为LowerCaseInputStream • 扩展其方法read()和read(byte[] b,int offset,int len),实现小写转换。 • 转小写使用Character.toLowerCase(char ch)方法。

  39. 作业 • 查阅java文档,分析其输出流的相关类是否也是按装饰模式设计的。并绘制相应的类图。

More Related