head-first-design-pattern
Table of Contents
- Chapter 1: Intro to Design Patterns
- It started with a simple SimUDuck app
- But now we need the ducks to FLY
- But Something went horribly wrong
- How about an interface
- Zeroing in on the problem…
- Separating what changes from what stays the same
- Designing the Duck Behaviors
- Implementing the Duck Behavior
- Integrating the Duck Behavior
- Testing the Duck code
- Setting behavior dynamically
- The Big Picture on encapsulated behaviors
- HAS-A can be better than IS-A
- Chapter 2: The Observer Pattern : Keep your objects in the know
- The Weather Monitoring application overriew
- Unpacking the WeatherData class
- Taking a first, misguided SWAG at the Weather Station
- What's wrong with our implementation?
- The Observer Pattern defined
- The power of Losse Coupling
- Designing the Weather Station
- Using Java's build-in Observer Pattern
- The dark side of java.util.Observeable
- Chapter 3: the Decorator Pattern Decorating Objects
- Chapter 4: the Factory Pattern: Baking with OO Goodness
- Thinking about "new"
- What's wrong with "new"?
- Identifying the aspects that vary
- Building a simple pizza factory
- Reworking the PizzaStore class
- The Simple Factory defined
- Franchising the pizza store
- We've seen one approach…
- But you'd like a little more quality control
- A framework for the pizza store
- Declaring a factory method
- We're just missing one thing: PIZZA!
- It's finally time to meet the Factory Method Pattern
- Factory Method Pattern defined
- A very dependent PizzaStore
- The Dependency Inversion Principle
- Applying the Principle
- A few guidelines to help you follow the Pinciple…
- Ensuring consistency in your ingredients
- Building the ingredient factories
- Building the New York ingredient factory
- Reworking the pizzas
- Revisiting our pizza stores
- What have we done?
- Abstract Factory Pattern defined
- Chapter 5: the Singleton Pattern: One of a Kind Objects
- Chapter 6: the Command Pattern: Encapsulating Invocation
- Chapter 7: the Adapter and Facade Patterns: Being Adaptive
- Chapter 8: the Template Method Pattern: Encapsulating Algorithms
- Chapter 9: the Iterator and Composite Patterns: Well-Managed Collections
- Chapter 10: the State Pattern: The State of Things
- Chapter 11: the Proxy Pattern: Controlling Object Access
Chapter 1: Intro to Design Patterns
It started with a simple SimUDuck app
- Joe公司的要设计一款有很多种类型鸭子的游戏.所以很自然的,他们首先创建了一个superclass
SimUDuck
Figure 1: duck-super.png
- 从上例我们可以看到:
- Duck是superclass
- 在其内部就实现了quack()和swim()
- 而把display()设计成abstract的, 因为所有的子类型display出来都是不同的.
But now we need the ducks to FLY
- 后来公司面对激烈的市场竞争,决定增加自己游戏里面鸭子的"能力",原来只会游泳和叫 现在我们要这个鸭子会飞!
- 最简单的实现方法就是在superclass里面加上一个fly() 函数,这下多有的subclass就
都会飞啦!
Figure 2: duck-ad-fly.png
But Something went horribly wrong
- 增加了一个fly函数以后,看似让所有的鸭子都增加了新功能,但同时也带来了设计的错 误,因为不是所有的鸭子都会飞,比如"橡胶鸭子"就不会飞
- 一个可行的方案是把rubber duck的fly method override.
class RubberDuck extends Duck { //.... void fly() { // Override to do nothing} }
- 但是override终究只是"权宜之计",一旦我们加入一个新的"木头鸭子", 它既不能"飞" 也不能"叫",我们总不能把fly和quack都override了吧.所以明显的,问题是出在了我们 的设计上
How about an interface
- Joe的解决办法是把某些"行为"(比如fly或者quack)从superclass里面独立出来,放到
某些接口里面,如果这个鸭子拥有这个"行为"的能力,那么就去implements这个接口
Figure 3: duck-interface.png
- 这种解决方法, 虽然解决了一部分问题,但是依然不好, 因为它
破坏了"code reuse": 因为java的interface里面的函数都没有实现代 码,我们要为每个行为在每个subclass里面实现一遍.
Zeroing in on the problem…
- 既然继承和interface都不好使,我们就要使用新的方法来处理这个问题了.新的方法就 是我们要介绍的Design Pattern
- Design Pattern一般都是长相相似的某些代码,其核心又是遵循了某些Design Principle
比如,要解决我们的duck问题,我们就要遵循下面的一个Design Principle
Identify the aspects of your application that vary and spearate them from what statys the same
- 把"变动"的和"不变"吧部分分开.这个principle虽然简单,但是却基本上为所有的design pattern所用.
Separating what changes from what stays the same
- 既然制定了大方向,那么我们就依次开始重构我们的代码:
- 变动的部分: fly, quack这些函数肯定是"变动的部分",要把他们从superclass拿出来
- 不变的部分: display, swim不改变,还留在superclass
Designing the Duck Behaviors
- 好了,我们先开始实现"变动的部分"也就是behavior: 把fly, quack从superclass拿出 来这个行动,和前面的interface解法相似. 所以仅仅把这个两个行为抽象成interface 然后让subclass继承这条路是行不通的.
- 我们要让另外的一些class去继承这些接口, 而!不!是!让subclass去继承.
- 我们的subclass再去包含这些class的supertype就可以了. 所谓supertype,其实就是 实现这些类的interface! 我们还可以在运行时动态的改变这些class, 因为保存的是 superctype(interface), 所以所有implements这个interface的class我们都可以包含
- 而原来的做法是:
- 要么从superclass获取实现(implementation)
- 要么自己实现(implement)一个函数体
- 用包含interface的办法来替代"获取implementation"的办法就是另外一个Design principle
Program to an interface, not an implementation
Implementing the Duck Behavior
- 下面就是那些implements接口的class们
Figure 4: duck-behavior.png
- 这些class由于不再受duck的控制,你甚至可以被Bird或者是Animal来"包含"这些behavior class
Integrating the Duck Behavior
- 我们的做法不再是把fly, quack等行为放到superclass里面实现,或者放到subclass里 面实现,而是在上面的class里面实现好了.现在我们来"包含"它们.包含的办法就是:创 建两个变量分别指向我们需要的行为. 后面我们会看到,这种"包含"在java里面叫做组 合(composition)
- 我们的相应的fly或者quack行为需要重新设计函数,在其内部调用刚才的变量来实现真
正的动作,下面以quack为例(fly也一样)
public class Duck { QuackBehavior quackBehavior; //more public void performQuack() { quackBehavior.quack(); } }
- 下面我们要看看这两个instance variable是如何在subclass中被实例化的
public class MallardDuck extends Duck { public MallardDuck() { quackBehavior = new Quack(); flyBehavior = new FlyWithWings(); } }
Testing the Duck code
- 首先是Duck class的代码
package org.hfeng.misc.hfdp.ch1.strategy; public abstract class Duck { FlyBehavior flyBehavior; QuackBehavior quackBehavior; public Duck() {} public abstract void display(); public void performFly() { flyBehavior.fly(); } public void performQuack() { quackBehavior.quack(); } }
- 其次是fly behavior的代码
package org.hfeng.misc.hfdp.ch1.strategy; public interface FlyBehavior { public void fly(); } package org.hfeng.misc.hfdp.ch1.strategy; public class FlyNoWay implements FlyBehavior { public void fly() { System.out.println("Fly No Way!"); } } package org.hfeng.misc.hfdp.ch1.strategy; public class FlyWithWings implements FlyBehavior { public void fly() { System.out.println("Fly with Wings"); } }
- quack behavior的代码
package org.hfeng.misc.hfdp.ch1.strategy; public interface QuackBehavior { public void quack(); } package org.hfeng.misc.hfdp.ch1.strategy; public class Quack implements QuackBehavior { public void quack() { System.out.println("Quack normally"); } } package org.hfeng.misc.hfdp.ch1.strategy; public class Squeak implements QuackBehavior { public void quack() { System.out.println("Squeak"); } } package org.hfeng.misc.hfdp.ch1.strategy; public class MuteQuack implements QuackBehavior { public void quack() { System.out.println("Mute Quack"); } }
- 某一种鸭子MallardDuck的代码
package org.hfeng.misc.hfdp.ch1.strategy; public class MallardDuck extends Duck { public MallardDuck() { flyBehavior = new FlyWithWings(); quackBehavior = new Quack(); } public void display() { System.out.println("This is a MallardDuck"); } }
- 测试代码
package org.hfeng.misc.hfdp.ch1.strategy; public class MiniDuckSimulator { public static void main(String[] args) { Duck mallard = new MallardDuck(); mallard.performFly(); mallard.performQuack(); } } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // Fly with Wings // // Quack normally // ////////////////////////////////////////////////////
Setting behavior dynamically
- 我们还可以在duck中增加函数setter,从而可以做到在run time更改鸭子的行为(其实
也就是让两个变量ref到其他对象)
public abstract class Duck { //... public void setFlyBehavior(FlyBehavior fb) { flyBehavior = fb; } public void setQuackBehavior(QuackBehavior qb) { quackBehavior = qb; } }
- 新建一个默认不能fly的duck
package org.hfeng.misc.hfdp.ch1.strategy; public class ModelDuck extends Duck{ public ModelDuck() { flyBehavior = new FlyNoWay(); quackBehavior = new Quack(); } public void display() { System.out.println("I'm a model duck"); } }
- 增加一种依靠"火箭"飞行的方法
package org.hfeng.misc.hfdp.ch1.strategy; public class FlyRocketPowered implements FlyBehavior { public void fly() { System.out.println("I'm flying with a rocket!"); } }
- 测试runtime更改行为
package org.hfeng.misc.hfdp.ch1.strategy; public class ModelDuckSimulator { public static void main(String[] args) { Duck model = new ModelDuck(); model.performFly(); model.setFlyBehavior(new FlyRocketPowered()); model.performFly(); } } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // I can't fly! // // I'm flying with a rocket! // ////////////////////////////////////////////////////
The Big Picture on encapsulated behaviors
- 好了,我们从从下图就可以看到整个的类图全貌.
Figure 5: duck-summary.png
- 这是我们第一个design pattern叫做strategy pattern, 定义如下, 用处不大
The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
HAS-A can be better than IS-A
- 所谓HAS-A的关系,就是我们这里说的每个duck都has a "Fly Behavior" and a "Quack Behavior". 也就是我们说的组合(composition)关系
- 关于composition,我们也有一个design principle
Favor composition over inheritance
- 实际的情况也是composition拥有比inheritance更多的优点.主要表现在:
- 包装一系列的其他类的算法, 却没有继承负担
- 可以在runtime更改行为
Chapter 2: The Observer Pattern : Keep your objects in the know
The Weather Monitoring application overriew
- 现在我们手头有一个项目, 需要从Weather Station读取数据,然后将数据显示出来.这
个项目有三个主体:
- Weather Station: 是物理设备,读取三个参数湿度,温度,气压
- WeatherData Object: 能够从Weather Station读取数据并且传递给Display device
- Display Device: 能够从WeatherData Object那里得到数据并显示.
- Weather Station喝Display Device等硬件的部分,我们不需要操心,我们只需要设计并 实现好软件部分也就是WeatherData Object就好
Unpacking the WeatherData class
- WeatherData的类图如下
+---------------------+ | WeatherData | +---------------------+ |getTemperature() | |getHumidity() | |getPressure() | |measurementsChanged()| |//... | +---------------------+
- getter函数主要的功能是从Weather Station读取最新的测量数据, 其内部如何实现我 们不必关心
- 我们需要关心的是函数measurementsChanged(), 这个函数是我们和Weather Station交 互的函数,每当Weather Station发现了自己测量的数据有变动的时候,就会调用WeatherData Object的measurementsChanged()函数. 所以这个函数是要我们来实现的.
Taking a first, misguided SWAG at the Weather Station
- 我们来看一个最朴素的WeatherData Object的实现方法
public class WeatherData{ // instance variable declarations public void measurementsChanged() { float temp = getTemperature(); float humidity = getHumidity(); float presure = getPressure(); //Three devices get the update currentConditionDisplay.update(temp,humidity, pressure); statisticsDisplay.update(temp,humidity, pressure); forecastDisplay.update(temp,humidity, pressure); } }
What's wrong with our implementation?
- 上面的实现,问题一大堆:
- 我们update Device的方法是,把每个具体的实现(implementation)都放入了函数内 部, 这违反了design principle(Program to an interface, not an implementation) 从而导致无法再runtime 增加或删除display设备
- 既然display设备是有可能"改变的部分",我们要独立出来实现
- 三个display对象的update函数参数都一样,我们最少要让他们实现同一个接口.
- 更改的方法就是Observer Pattern
The Observer Pattern defined
- Observer Pattern定义
The Observer Pattern defines a one-to-many dependency between objects so that when one object changes state, all of its dependents are notified and updated automatically.
- 我们习惯上把更改的那个object叫做subject(话题), 而被通知的所有订阅这个subject 的object叫做observer(观察者), 这也是名字的来历
- 下面是observer pattern的类图
Figure 6: observer.png
The power of Losse Coupling
- Observer Pattern的设计也同时遵从了一个Design Principle
Strive for loosely coupled designs between objects that interact
- subject和observer之间的联系就很少, subject对observer唯一的了解就是observer肯 定会implements一个接口,从而能够"肯定实现一个函数"
- 这样一来,observer改动的自由度就很大,比如上例中一种完全崭新的display的方法, 只需要继承一个接口,那么subject也会快乐接受
- 由于observer遵从了某一种接口,那么我们subject可以在runtime随意增加删除observer
- 只要保证这个"松耦合"不变,我们更改两个类内部的什么其他部分都不会影响工作. 所 以"松耦合"是非常好的一种设计哲学.
Designing the Weather Station
- 整个系统的设计类图如下
Figure 7: weather-data.png
- 好的,我们来看看代码实现.有些细节还是代码里面才能体现
- 首先看Subject interface
package org.hfeng.misc.hfdp.ch2.observer.plain; public interface Subject { public void registerObserver(Observer o); public void removeObserver(Observer o); public void notifyObservers(); }
- WeatherData实现了这个接口.
package org.hfeng.misc.hfdp.ch2.observer.plain; import java.util.ArrayList; public class WeatherData implements Subject { private ArrayList<Observer> observers; private float temperature; private float humidity; private float pressure; public WeatherData() { observers = new ArrayList<Observer>(); } public void registerObserver(Observer o) { observers.add(o); } public void removeObserver(Observer o) { int i = observers.indexOf(o); if (i >= 0) { observers.remove(i); } } public void notifyObservers() { for (int i = 0; i < observers.size(); i++) { Observer observer = observers.get(i); observer.update(temperature, humidity, pressure); } } //此函数一般是WeatherStation调用,在更新的情况下,通知所有的observer public void measurementsChanged() { notifyObservers(); } public void setMeasurements(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; measurementsChanged(); } }
- Observer interface的代码如下
package org.hfeng.misc.hfdp.ch2.observer.plain; public interface Observer { public void update(float temperature, float humidity, float pressure); }
- DisplayElement interface的代码如下
package org.hfeng.misc.hfdp.ch2.observer.plain; public interface DisplayElement { void display(); }
- 任意一个display都可以如下的实现上面两个接口, 我们以其中一个display为例,其
他display都类似
package org.hfeng.misc.hfdp.ch2.observer.plain; public class CurrentConditionDisplay implements Observer, DisplayElement { private float temperate; private float humidity; // 包含了一个指向ConcreteSubject的ref, 这样可以在ctor的时候注册进去. // 当然也可以明确的在"外部"注册."内部"注册反而更不容易错 private Subject weatherData; public CurrentConditionDisplay(Subject weatherData){ this.weatherData = weatherData; weatherData.registerObserver(this); } public void update(float temperature, float humidity, float pressure) { this.temperate = temperature; this.humidity = humidity; display(); } public void display() { System.out.println("Current condition: " + temperate + "F degree and " + humidity + "% humidity"); } }
- 好,我们最后来写个main例子来测试我们的display
package org.hfeng.misc.hfdp.ch2.observer.plain; public class WeatherStation { public static void main(String[] args) { WeatherData weatherData = new WeatherData(); CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData); StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData); ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData); weatherData.setMeasurements(80, 65, 30.4f); System.out.println(); weatherData.setMeasurements(82, 70, 29.2f); } } //////////////////////////////////////////////////////// // <===================OUTPUT===================> // // Current condition: 80.0F degree and 65.0% humidity // // Avg/Max/Min temperate = 80.0/80.0/80.0 // // Forecast: Improving weather on the day // // // // Current condition: 82.0F degree and 70.0% humidity // // Avg/Max/Min temperate = 81.0/82.0/80.0 // // Forecast: Watch out for cooler, rainy weather // ////////////////////////////////////////////////////////
- 首先看Subject interface
Using Java's build-in Observer Pattern
- 由于observer模式是如此广泛的存在.java为其设计了内置的支持:
- 观察者这边是interface Observer:
package org.hfeng.misc.hfdp.ch2.observer.observable; import java.util.Observable; import java.util.Observer; public class CurrentConditionsDisplay implements Observer, DisplayElement { Observable observable; private float temperature; private float humidity; public CurrentConditionsDisplay(Observable observable) { this.observable = observable; observable.addObserver(this); } public void update(Observable obs, Object arg) { if (obs instanceof WeatherData) { WeatherData weatherData = (WeatherData)obs; this.temperature = weatherData.getTemperature(); this.humidity = weatherData.getTemperature(); display(); } } public void display() { System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity"); } }
- 订阅者这边是class Observable:
package org.hfeng.misc.hfdp.ch2.observer.observable; import java.util.Observable; public class WeatherData extends Observable { public float getTemperature() { return temperature; } public float getHumidity() { return humidity; } public float getPressure() { return pressure; } private float temperature; private float humidity; private float pressure; public WeatherData(){ } public void measurementsChanged() { setChanged(); notifyObservers(); } public void setMeasurements(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; measurementsChanged(); } }
- 用户使用
package org.hfeng.misc.hfdp.ch2.observer.observable; public class WeatherStation { public static void main(String[] args) { WeatherData weatherData = new WeatherData(); CurrentConditionsDisplay currentConditionDisplay = new CurrentConditionsDisplay(weatherData); StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData); ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData); weatherData.setMeasurements(82, 70, 29.2f); System.out.println(); weatherData.setMeasurements(82, 70, 29.2f); } } ////////////////////////////////////////////////////////// // <===================OUTPUT===================> // // Forecast: Watch out for cooler, rainy weather // // Avg/Max/Min temperate = 82.0/82.0/82.0 // // Current conditions: 82.0F degrees and 82.0% humidity // // // // Forecast: More of the same // // Avg/Max/Min temperate = 82.0/82.0/82.0 // // Current conditions: 82.0F degrees and 82.0% humidity // //////////////////////////////////////////////////////////
- 观察者这边是interface Observer:
- 上面的Observable的使用有些门道:
- 你必须首先设置内部的state为changed,然后才能进行notify
- measurementsChanged()按说是被调用者使用的,这里被setMeasurements包含,然后 被用户调用
- IMPORTANT! 这次display的顺序和上次不同! 上次是按照我们注册的顺序C->A->F,
而这次顺序却反过来了!F->A-C, 这是因为java实现时候的notify顺序和我们的不同
所以
Never depend on order of evaluation of the Observer notifications
The dark side of java.util.Observeable
- java的observable设计的不太好,限制了它发挥自己的功能:
- Observable is a class:
- 这没有必要,因为我们必须继承Observerable, 而我们如果已经有一套成熟代码 的情况下想"获取Observable这种能力",那么我们implement接口就好了(脑补 Runnable), 但是设计成class就没办法了
- 你甚至不能提供自己的observable实现,比如多线程安全版本的
- Observable 的关键函数的protected的: 如果你看了代码你会发现Observable最关键的函数setChanged()是protected,这就 说明你必须得subclass才能调用,连composition这条路都堵上了.
- Observable is a class:
Chapter 3: the Decorator Pattern Decorating Objects
Welcome to Starbuzz Coffee
- Starbuzz咖啡是一个非常出名的品牌,他们店内有非常多的饮料,具体类图如下Beverage
是一个abstract class
Figure 8: first-beverage.png
- 但是你想到了没有,如果在原有的饮料基础上,新加一些调料的话,会不停的增加新的类,
然后就会出现所谓的"class explosion"(很多没必要的类都会出现):
Figure 9: explosion-beverage.png
- 如果不想出现"class explosion",我们也在abstract class里面设置一些instance
variable, 然后其他class继承了这个superclass就有这些"变化"了
Figure 10: instance-beverage.png
The Open-Closed Principle
- 上面这种做法看似正确,其实也是有问题,因为它违反了如下的design principle
Classes should be open for extension, but closed for modification.
- 其实说白了,我们的目标很简单,就是能够更快的把功能加入到已有的代码,而不破坏已 有代码
Meet the Decorator Pattern
- Decorator pattern的类图如下
Figure 11: decorate-component.png
- 什么是decorator pattern呢?就是说:
- 我们有个基本的咖啡DarkRoast
- 第一步"装饰": Mocha
- 第二步"装饰": Whip
- 这样一来调用cost()的时候,就可以根据装饰的过程来计算最后的价格
- 这个过程听起来是有些难以理解,看看类图:
Figure 12: decorate-beverage.png
- 类图理解如下
- Beverage就是component
- Espresso等咖啡就是具体的component(ConcreteComponent)
- Milk, Soy等"作料"也想成为和Espresso等一样的Concretecomponent,无奈自己能力 有限,所以要依靠decorator
- CondimentDecorator(作料decorator), 把自己装扮成"ConcreteComponent"
Coding Beverage
- 再来看看代码:
- Beverage就是一个abstract class
package org.hfeng.misc.hfdp.ch3.starbuzz; public abstract class Beverage { String description = "Unknown Beverage"; public String getDescription() { return description; } public abstract double cost(); }
- Espresso是具体的咖啡,是咖啡店的主力
package org.hfeng.misc.hfdp.ch3.starbuzz; public class Espresso extends Beverage { public Espresso() { description = "Espresso"; } public double cost() { return 1.99; } }
- CondimentDecorator,目的是:让"作料"也有主力咖啡样子的
package org.hfeng.misc.hfdp.ch3.starbuzz; public abstract class CondimentDecorator extends Beverage{ public abstract String getDescription(); }
- Milk是"作料",它继承了CodimentDecorator,从此有了主力咖啡的能力
package org.hfeng.misc.hfdp.ch3.starbuzz; public class Milk extends CondimentDecorator { Beverage beverage; public Milk(Beverage beverage) { this.beverage = beverage; } public String getDescription() { return beverage.getDescription() + ", Milk"; } public double cost() { return .10 + beverage.cost(); } }
- 最后我们来看看测试代码,用法很特别
package org.hfeng.misc.hfdp.ch3.starbuzz; public class CoffeeTest { 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()); } } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // Espresso $1.99 // // Dark Roast Coffee, Mocha, Mocha, Whip $1.49 // // House Blend Coffee, Soy, Mocha, Whip $1.34 // ////////////////////////////////////////////////////
- Beverage就是一个abstract class
Chapter 4: the Factory Pattern: Baking with OO Goodness
Thinking about "new"
- 当你要使用new的时候,其实你是在"实例化"一个concrete class.所以,结果必须是一个 class,而不是一个interface.
- 而且你以后会发现,使用new来创建一个concrete class会让你的代码more fragile 并 且less flexible.
- 假设,你有一系列已知的concrete class, 你要想在运行时知道使用哪个concrete class
必须写如下的代码
Duck duck; if (picnic) { duck = new MalardDuck(); } else if (hunting) { duck = new DecoyDuck(); } else if (inBathTub) { duck = new RubberDuck(); }
- 如果类似上面一样代码出现在你代码的多个地方的时候,维护以及更新"这些代码"都会 是nightmare
What's wrong with "new"?
- 其实从技术角度上来讲, new并没有什么错误,错误还是在于我们的老朋友CHANGE(改变):
- 新的类型的"鸭子"可能会加入
- 老的类型的"鸭子"可能会不再支持
- 如果上述CHANGE不停出现,你在代码里找到那些if else,并且更改,是非常麻烦的事情, 所以,我们必须做些改变
Identifying the aspects that vary
- "判断哪些部分是以后可能改动的"是我们的design principle之一
- 假设我们有个披萨店, 然后就有一个"订购披萨"的函数
Pizza orderPizza() { Pizza pizza = new Pizza(); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; }
- 但是我们需要多种类型的披萨,所以传入个参数来引入多种披萨
Pizza orderPizza(String type) { Pizza pizza; if (type.equals("cheese")) { pizza = new CheesePizza(); } else if (type.equals("greek")) { pizza = new GreekPizza(); } else if (type.equals("pepperoni")) { pizza = new PepperoniPizza(); } pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; }
- 仔细分析代码,这段代码还是破坏了我们的design principle:"代码必须对扩展开放,
但是不要运行更改内部代码", 因为如果我们要"增加新披萨",或者是"删除老披萨",必
然要改动这个函数, 比如
Pizza orderPizza(String type) { Pizza pizza; if (type.equals("cheese")) { pizza = new CheesePizza(); } else if (type.equals("pepperoni")) { pizza = new PepperoniPizza(); } else if (type.euqls("clam")) { // newly added pizza = new ClamPizza(); } pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; }
- 经过这个改动,我们也就知道如下部分是"不变"的,我们需要跟"变化"的部分分开
pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza;
- 也就是说,变化的部分是如下
if (type.equals("cheese")) { pizza = new CheesePizza(); } else if (type.equals("pepperoni")) { pizza = new PepperoniPizza(); } else if (type.euqls("clam")) { // newly added pizza = new ClamPizza(); }
- 一个最直观的想法就是把改动的这个部分封装在一个函数里面,这样所有用到这段代码 的地方,只需要更改这个函数就可以了.这看起来好像是DRY(Don't repeat yourself) 的感觉
- 在design pattern的世界里面,我们把抽象出来的这个函数叫做Factory, 这个名字也 很形象: 我们每次都需要instance的时候,直观跟工厂要,产品更新换代这些事情,就交 给"工厂"来处理吧.
Building a simple pizza factory
- 好了,我们现在来看看"抽出来"的代码实现factory的代码
public class SimplePizzaFactory { public Pizza createPizza(String type) { Pizza pizza = null; if (type.equals("cheese")) { pizza = new CheesePizza(); } else if (type.equals("pepperoni")) { pizza = new PepperoniPizza(); } else if (type.equals("clam")) { pizza = new ClamPizza(); } else if (type.equals("veggie")) { pizza = new VeggiePizza(); } return pizza; } }
- 这段代码"抽出来"以后,貌似只是"换了一个地方增加或删除披萨类型",但是因为这段代 码会在"其他多个地方使用",所以"抽出来"以后,你可以只改动一个地方,就"影响"其他 的使用它的类了.
- 这个是factory的代码,还有一种叫做static factory代码, static factory的代码,就
是把createPizza函数设计成static的,这样做有优点也有缺点:
- 优点是不需要实例化SimplePizzaFactory就可以使用createPizza
- 缺点是不能通过"继承"SimplePizzaFactory来更改createPizza的实现.
Reworking the PizzaStore class
- 已经把"改动的"抽出来了,然后我们再看看把"抽出来"的部分,加载到原来的"不变的"
部分中去
public class PizzaStore { // As we use factory, not `static factory`, we have to have the factory // instance first. SimplePizzaFactory factory; public PizzaStore(SimplePizzaFactory factory) { this.factory = factory; } public Pizza orderPizza(String type) { Pizza pizza; // `operator new` is repalced with `method createPizza` pizza = factory.createPizza(type); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } // other methods }
The Simple Factory defined
- 请屏住呼吸,接受下面的一个事实
我们前面讨论的一切叫做`Simple Factory`, 它并不是设计模式的一种! 介绍它是因为它广泛的存在和使用
- 下面就是simple factory的类图
Figure 13: simple-factory.png
Franchising the pizza store
- btw, franching在这里是"特许经营"的意思
- 随着你的披萨越做越好,很多人想"加盟"你的品牌.特许加盟是有较高的利润,但是同时
也要解决两个问题:
- 为了保证自己的产品质量不会因为"新加盟商"的把控不力而砸了牌子
- 每个加盟商都在不同的地区,都有"地区化的特殊"要求,换句话说,会有不同的的 prepare(),或者bake()函数实现
We've seen one approach…
- 如果利用我们上面学到的知识,我们可以使用Simple Factory来实现的话,会是一个非常
好的approach, 比如我们可以extends Simple Factory来创建新的Factory比如:
- New York: 就创建个新的NYPizzaFactory
- Chicago: 就创建个新的ChicagoPizzaFactory
- California: 就创建个CaliforniaPizzaFactory
But you'd like a little more quality control
- 如果让加盟商自己的Factory来实现bake()等重要函数, 如果供应商在这上面做了什么 手脚的话,其肯定会对我们的品牌有影响
A framework for the pizza store
- 好,总结起来,我们对"加盟"这件事情,有如下两点要求:
- 我们对于prepare(), bake()等工作要求甚高,不能由"加盟商"实现,所以我们要保护 好这些代码
- 同时我们要给"加盟商"一些选择自己的披萨类型的权利.注意是'选择披萨类型的权利', 而不是'更改烹饪方法的权利'
- 下面就是一种能够满足上述两种要求的做法, 首先在PizzaStore里面加一个abstract
method(所以PizzaStore自己也就是abstract的了)
public abstract class PizzaStore { public Pizza orderPizza(String type) { Pizza pizza; pizza = createPizza(type); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } abstract Pizza createPizza(String type); }
- 这样做的直观感受就是"factory被变成了一个abstract method"
- 然后,我们让其他的加盟商来extends我们的PizzaStore, 然后在子类里面Override最
重要的函数createPizza, 因为这个函数约束了其返回值必须是Pizza类型的.所以只需
要要求所有的Pizza(包括其子类)都是我们"供应商"提供的,就可以了!
public class NYPizzaStore extends PizzaStore { Pizza createPizza(String item) { if (item.equals("cheese")) { return new NYStyleCheesePizza(); } else if (item.equals("veggie")) { return new NYStyleVeggiePizza(); } else if (item.equals("clam")) { return new NYStyleClamPizza(); } else if (item.equals("pepperoni")) { return new NYStylePepperoniPizza(); } } }
Declaring a factory method
- abstract createPizza method其实就是一个起到factory作用的method.简称factory
method,其特征一般如下
abstract Product factoryMethod(String type)
- factory method的特征分析:
- 首先factory method必须是abstract的, 所以才能让subclass来进行object的creation 的活动
- 其次,其返回值一般会为superclass所用, superclass也知道其接口
- factoryMethod也是可以有参数的,用来选择不同的类型.
- factoryMethod其实就是让使用它代码的client(orderPizza() in superclass), 不去 了解它要去处理的exactly的类型(只知道个大概,也就是Pizza, 不知道具体类型)
We're just missing one thing: PIZZA!
- 基类的Pizza是如下的样子
public abstract class Pizza { String name; String dough; String sauce; ArrayList toppings = new ArrayList(); void prepare() { System.out.println("Prepareing" + name); } }
- 基类是我们供应的,但是加盟商希望更多样话更本地化,我们就sub一个芝加哥的子类
public class ChicagoStyleCheesePizza extends Pizza { public ChicagoStyleCheesePizza() { name = "Chicago Style Deep Dish Cheese Pizza"; dough = "Extra Thick Crust Dough"; sauce = "Plum Tomato Sauce"; toppings.add("Shredded Mozzarella Cheese"); } // The chicago style pizza also overrides the cut() method // so that the pieces are cut into square. void cut() { System.out.println("Cutting the pizza into square slices"); } }
- 好,我们来main函数,来看我们怎么使用这些store和pizza
package org.hfeng.misc.hfdp.ch4.factory.method; public class MainTest { public static void main(String[] args) { PizzaStore nyStore = new NYPizzaStore(); PizzaStore chicagoStore = new ChicagoPizzaStore(); Pizza pizza = nyStore.orderPizza("cheese"); System.out.println("Adam ordered a " + pizza.getName() + "\n"); pizza = chicagoStore.orderPizza("cheese"); System.out.println("Bell ordered a " + pizza.getName() + "\n"); } } /////////////////////////////////////////////////////////// // <===================OUTPUT===================> // // --- Making a NY Style Cheese Pizza --- // // Preparing NY Style Cheese Pizza // // Grated Reggiano Cheese // // Bake for 25 minutes at 350 // // Cutting the pizza into diagonal slices // // Place pizza in official PizzaStore box // // Adam ordered a NY Style Cheese Pizza // // // // --- Making a Chicago Style Deep Dish Cheese Pizza --- // // Preparing Chicago Style Deep Dish Cheese Pizza // // Shredded Mozzarella Cheese // // Bake for 25 minutes at 350 // // Cutting the pizza into square slices // // Place pizza in official PizzaStore box // // Bell ordered a Chicago Style Deep Dish Cheese Pizza // ///////////////////////////////////////////////////////////
It's finally time to meet the Factory Method Pattern
- factory-like pattern有一个特点叫
封装object创建过程: encapsulate object creation
- Factory Method Pattern的封装过程很特别: 其是
让子类来决定生产什么样的对象
- 下面是factory method pattern的类图
Figure 14: factory-method.png
- 为了让子类来决定生产什么样的对象, 我们必须在父类里面声明一个abstact method 也就是createPizza()
- 而真正生产pizza的class是NYPizzaStore(也就是加盟商),这种"真正的生产product" 的class叫做concrete creator
- abstract class Pizza是 abstract class PizzaStore里面生产的, 而真实的类型则 是NYStyleCheesePizza这种,也叫concrete product
- 更加重要的是, factory method pattern是一个"生产者"和"产品"相互对应的形式:
- Abstract Factory produces Abstract Product
- Concrete Factory produces Concrete Product
Factory Method Pattern defined
- 定义还是如此的艰涩
The Factory Method Pattern: defines an interface for creating an object, but lets subclasses decide which class to instantiate, Factory Method lets a class defer instantiation to subclasses.
A very dependent PizzaStore
- 粗看起来Factory Method pattern好像没什么用, 因为要理解它的作用,要先来个反例:
下面这个例子没有使用任何一种factory, 我们的Store就直接依赖于各种的pizza object
public class DependentPizzaStore { public Pizza createPizza(String style, String type) { Pizza pizza = null; if (style.equals("NY")) { if (type.equals("cheese")) { pizza = NYStyleCheesePizza(); } else if (type.equals("clam")) { pizza = NYStyleClamPizza(); } else if (type.equals("veggie")) { pizza = NYStyleVeggiePizza(); } } else if (style.equals("Chicago")) { if (type.equals("cheese")) { pizza = ChicagoStyleCheesePizza(); } else if (type.equals("clam")) { pizza = ChicagoStyleClamPizza(); } else if (type.equals("veggie")) { pizza = ChicagoStyleVeggiePizza(); } } else { return null; } pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } }
- 所以我们每增加一个类型的pizza,我们就要更改一次PizzaStore,因为我们的PizzaStore
是"直接依赖concrete pizza类型的"
Figure 15: very-dependent-pizzastore.png
The Dependency Inversion Principle
- 我们可以看到,减少对"concrete class"的依赖,是一件非常好的事情.我们甚至为这条
增加了一个design principle
Depend upon abstractions. Do not depend upon concrete classes
- 这个principle看起来和"program to interface"相似,但是缺是一个"更强"的要求:
- "program to interface"更多的是要求high-level使用low-level的时候,要使用 low-level的接口
- 而我们当前的dependency inversion principle要求我们(high-level就是决定
low-level的类,比如这里是store, 而low-level就是pizza:
- high-level依赖low-level的接口
- low-level也要同时依赖high-level的接口
Applying the Principle
- 前面的Very Dependent PizzaStore的问题就是会depends on'每一种pizza'.其实我们 的PizzaStore如果考虑'每一种pizza'就会相互的dependency很重
- 我们要通过apply 'dependency inversion principle'来减少"两个方向上的依赖":也
就是我们前面的Factory Method Pattern. 我们可以看到,apply factory method pattern
以后,high-level的PizzaStore和low-level的concrete pizza都依赖于"abstract pizza"
Figure 16: apply-principle-pizzastore.png
A few guidelines to help you follow the Pinciple…
- 坚持如下几点设计的"外在要求",能够保证不破坏Dependency Inversion Principle:
- No variable should hold a reference to a concrete class: 如果你使用了new的 话, 那么你就hold了一个concrete class的reference,肯定就依赖于这个concrete class, 使用factory来返回一个interface,在runtime来确定是什么concrete class
- No class should derive from a concrete class: 如果你继承一个concrete class, 那么肯定是依赖于这个class, 所以请继承一个interface(或者abstract class)
- No method should override an implementted method of any of its base classes: 依然用到了继承, 而且还是集成的abstract class, 那么base class的method已经实 现了的话,那么subclass是"最好不要"改动implementation,因为一旦改动,这个"is-a" 的关系,就不纯正了.
- 上面三条,都是"最好"坚持,因为不可能所有的情况下都不break这些条例.比如我们的 chicago pizza就行override了cut()函数
Ensuring consistency in your ingredients
- 我们已经标准化了生产过程,但是"加盟商"还会在"原料"上偷工减料,从而伤害我们的品 牌, 所以,我们还要对原料的来源进行"标准化"
- 而原料的标准化比较麻烦,因为原料都是原产地直供的:芝加哥的red sauce和纽约的 red sauce肯定不一样.
Building the ingredient factories
- 下面我们来创建一个"制造ingredient的factory", 这是个interface (pure abstract)
class
package org.hfeng.misc.hfdp.ch4.factory.abstractf; public interface PizzaIngredientFactory { public Dough createDough(); public Sauce createSauce(); public Cheese createCheese(); public Clam createClam(); }
- 在这个"抽象工厂"的基础上,我们要坐如下工作来满足我们的要求:
- 为每一个地区都创建一个PizzaIngredientFactory的subclass来创建"地区特色配料"
- 创建一系列"concrete ingredient", 比如ReggianoCheese, ThinCrustDough这些配 料是可以在不同的地区间分享的(如果一个ingredient每处都可得,当然可以)
- 最后我们还是需要一个PizzaStore来联系所有的东西
Building the New York ingredient factory
- 我们首先来看看为NY准备的sub class
package org.hfeng.misc.hfdp.ch4.factory.abstractf; public class NYPizzaIngredientFactory implements PizzaIngredientFactory { public Dough createDough() { return new ThinCrustDough(); } public Sauce createSauce() { return new MarinaraSauce(); } public Cheese createCheese() { return new ReggianoCheese(); } public Clam createClam() { return new FreshClam(); } }
Reworking the pizzas
- pizza的代码现在如下, 其为一个abstract class, 和它打交道的也都是一些abstract
的class(或者interface), 比如 Dough Sauce Clam等
package org.hfeng.misc.hfdp.ch4.factory.abstractf; public abstract class Pizza { String name; Dough dough; Sauce sauce; Cheese cheese; Clam clam; abstract void prepare(); void bake() { System.out.println("Bake for 25 minutest at 350"); } void cut() { System.out.println("Cuttng the pizza into diagonal slices"); } void box() { System.out.println("Place pizza in official PizzaStore box"); } void setName(String name) { this.name = name; } String getName() { return name; } public String toString() { StringBuilder result = new StringBuilder(); result.append("--- " + name + " ---\n"); if (dough != null) { result.append(dough); result.append("\n"); } if (sauce != null) { result.append(sauce); result.append("\n"); } if (cheese != null) { result.append(cheese); result.append("\n"); } if (clam != null) { result.append(clam); result.append("\n"); } return result.toString(); } }
- 只有abstract的Pizza是不够的,我们还要concrete的Pizza, 也只有在concrete的时候,
你才会发现, "配料工厂"才会进来和我们合作
package org.hfeng.misc.hfdp.ch4.factory.abstractf; public class CheesePizza extends Pizza { PizzaIngredientFactory ingredientFactory; public CheesePizza(PizzaIngredientFactory ingredientFactory) { this.ingredientFactory = ingredientFactory; } void prepare() { System.out.println("Preparing " + name); dough = ingredientFactory.createDough(); sauce = ingredientFactory.createSauce(); cheese = ingredientFactory.createCheese(); } }
- 当然你会发现,合作的也是"abstract factory", 原料的不同,会以"工厂的不同"来显
示出来. 从ClamPizza上再来看一遍
package org.hfeng.misc.hfdp.ch4.factory.abstractf; public class ClamPizza extends Pizza { PizzaIngredientFactory ingredientFactory; public ClamPizza(PizzaIngredientFactory ingredientFactory) { this.ingredientFactory = ingredientFactory; } void prepare() { System.out.println("Preparing " + name); dough = ingredientFactory.createDough(); sauce = ingredientFactory.createSauce(); cheese = ingredientFactory.createCheese(); clam = ingredientFactory.createClam(); } }
Revisiting our pizza stores
- PizzaStore必然和原来一样, 只是一个factory method来引入pizza的factory
package org.hfeng.misc.hfdp.ch4.factory.abstractf; public abstract class PizzaStore { protected abstract Pizza createPizza(String item); public Pizza orderPizza(String type) { Pizza pizza = createPizza(type); System.out.println("--- Making a " + pizza.getName() + " ---"); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } }
- 不同的subStore会确定不同的地区,从而引用不同的"配料工厂", 然后会根据口味的不
同来引入不同的pizza
package org.hfeng.misc.hfdp.ch4.factory.abstractf; public class NYPizzaStore extends PizzaStore { protected Pizza createPizza(String item) { Pizza pizza = null; PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory(); if (item.equals("cheese")) { pizza = new CheesePizza(ingredientFactory); pizza.setName("New York Style Cheese Pizza"); } else if (item.equals("clam")) { pizza = new ClamPizza(ingredientFactory); pizza.setName("New York Style Clam Pizza"); } return pizza; } }
What have we done?
- 我们为"不同地区,引入不同配料"的过程,其实是通过一种新的设计模式实现的:抽象工 厂 (Abstract Factory)
- 抽象工厂模式是让我们使用不同的工厂来为每一个不同的地区,每一个不同的环境来创 建一个新的"一系列的product"
- 抽象工厂不仅仅自己是抽象的,抽象工厂内部处理的product也是抽象的(一般是interface) 所以我们可以在sub factory中产生whatever的concrete product
- 最后我们来看看main函数是怎么创建一个抽象工厂的
package org.hfeng.misc.hfdp.ch4.factory.abstractf; public class MainTest { public static void main(String[] args) { PizzaStore nyStore = new NYPizzaStore(); PizzaStore chicagoStore = new ChicagoPizzaStore(); Pizza pizza = nyStore.orderPizza("cheese"); System.out.println("Nina ordered a " + pizza); pizza = chicagoStore.orderPizza("cheese"); System.out.println("Clark ordered a " + pizza); pizza = nyStore.orderPizza("clam"); System.out.println("Neil ordered a " + pizza); pizza = chicagoStore.orderPizza("clam"); System.out.println("Cindy ordered a" + pizza); } } ///////////////////////////////////////////////////////// // <===================OUTPUT===================> // // --- Making a New York Style Cheese Pizza --- // // Preparing New York Style Cheese Pizza // // Bake for 25 minutest at 350 // // Cuttng the pizza into diagonal slices // // Place pizza in official PizzaStore box // // Nina ordered a --- New York Style Cheese Pizza --- // // Thin Crust Dough // // Marinara Sauce // // Reggiano Cheese // // // // --- Making a Chicago Style Cheese Pizza --- // // Preparing Chicago Style Cheese Pizza // // Bake for 25 minutest at 350 // // Cuttng the pizza into diagonal slices // // Place pizza in official PizzaStore box // // Clark ordered a --- Chicago Style Cheese Pizza --- // // Thin Crust Dough // // Marinara Sauce // // Shredded Mozzarella // // // // --- Making a New York Style Clam Pizza --- // // Preparing New York Style Clam Pizza // // Bake for 25 minutest at 350 // // Cuttng the pizza into diagonal slices // // Place pizza in official PizzaStore box // // Neil ordered a --- New York Style Clam Pizza --- // // Thin Crust Dough // // Marinara Sauce // // Reggiano Cheese // // Fresh Clam from Long Island Sound // // // // --- Making a Chicago Style Clam Pizza --- // // Preparing Chicago Style Clam Pizza // // Bake for 25 minutest at 350 // // Cuttng the pizza into diagonal slices // // Place pizza in official PizzaStore box // // Cindy ordered a--- Chicago Style Clam Pizza --- // // Thin Crust Dough // // Marinara Sauce // // Shredded Mozzarella // // Frozen Clam from Chesapeake Bay // /////////////////////////////////////////////////////////
- 创建store的时候,就确定了区域,至于每个区域的clam是什么样子的,那不用你关心,store 自己内部会处理
Abstract Factory Pattern defined
- 好啦,让我们来看看abstract factory的具体定义
The Abstract Factory Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes
- 其核心的使用方法就是让client在创建的时候,完全使用抽象工厂,自己的函数也是只
处理抽象的对象:
- 比如,我们'具体的pizza CheesePizza'就是Abstract Factory的使用者(Client). 在
定义的时候, 内部是看不到concrete class的, PizzaIngredientFactory, dough,
sauce, cheese都是interface或者abstract class
package org.hfeng.misc.hfdp.ch4.factory.abstractf; public class CheesePizza extends Pizza { PizzaIngredientFactory ingredientFactory; public CheesePizza(PizzaIngredientFactory ingredientFactory) { this.ingredientFactory = ingredientFactory; } void prepare() { System.out.println("Preparing " + name); dough = ingredientFactory.createDough(); sauce = ingredientFactory.createSauce(); cheese = ingredientFactory.createCheese(); }
- 只有在runtime使用的时候,才会赋予一个concreteFactory, 从而生产出concretePizza,
注意是在if else里面的new instance的时候,传入一个concreteFactory
public class NYPizzaStore extends PizzaStore { protected Pizza createPizza(String item) { Pizza pizza = null; PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory(); if (item.equals("cheese")) { pizza = new CheesePizza(ingredientFactory); pizza.setName("New York Style Cheese Pizza"); } else if (item.equals("clam")) { pizza = new ClamPizza(ingredientFactory); pizza.setName("New York Style Clam Pizza"); } return pizza; } }
- 比如,我们'具体的pizza CheesePizza'就是Abstract Factory的使用者(Client). 在
定义的时候, 内部是看不到concrete class的, PizzaIngredientFactory, dough,
sauce, cheese都是interface或者abstract class
- 最后我们来看看Abstract Factory的类图,因为我们的例子中concrete Pizza是在一个
factory method中实现的,所以不是太明显,而在普通的Abstrct Factory中,其实这就是
一个Client Class
Figure 17: abstract-factory.png
Chapter 5: the Singleton Pattern: One of a Kind Objects
- 有很多的object在整个process中,我们只需要一个,如果实例化了超过一个对象,我们会
面对很多错误的行为,或者资源耗尽等错误,这类object有:
- thread polls
- caches
- dialog boxes
- objects that handle preferences and registery settings
- objects used for logging
- objects that act as device drivers to devices like printers
- Java中, static variable(全局变量)看起来也能达到"只有一个"的效果,但是显然singlton
显然是一个time-tested的方法.比如:
- 全局变量是在application加载的时候就被初始化了
- singleton可以在我们需要的时候再初始化
- 比如一个object是很珍贵的资源,如果我们初始化其为global variable,但是却从来不使
用,那就亏了. (注意end up doing的意思是: 最终做了something, end up相当于助词
'最终')
What if this object is resource intensive and your application never ends up using it?
- 当然现在业界也流传着singleton被"过度"使用的情况. 这个以后再表
Dissecting the classic Singleton Pattern implementation
- 我们保证一个object只被初始化一次的方法是private ctor,下面是第一个例子(当然
这个例子不完美)
public class Singleton { private static Singleton uniqueInstance; // other useful instance variables here private Singleton() {} public static Singleton getInstance() { if (uniqueInstance == null) { uniqueInstance = new Singleton(); } return uniqueInstance; } // other useful methods here }
The Chocolate Factory
- 现代的巧克力工厂(不是工厂模式)都有一个电脑控制的锅炉,用来混合巧克力和牛奶, 并加热
- 这个锅炉的程序书写要非常的小心,避免以下几种"BAD things"的出现:
- 巧克力和牛奶还没有加热就排除
- 锅炉已经满了还往里加牛奶或巧克力
- 加热一个空的锅炉
- 下面是我们的代码
public class ChocolateBoiler { private boolean empty; private boolean boiled; public ChocolateBoiler() { empty = true; boiled = false; } public void fill() { if (isEmpty()) { empty = false; boiled = false; // fill the boiler with a milk/chocolate mixture } } public void drain() { if (!isEmpty() && isBoiled()) { // drain the boiled milk and cholate empty = true; } } public void boil() { if (!isEmpty() && isBoiled()) { // bring the contents to a boil boiled = true; } } public boolean isEmpty() { return empty; } public boolean isBoiled() { return boiled; } }
- 这个代码非常认真的控制了一些边界条件,试图让bad things不发生. 但是,一个意想不
到的事情发生了,那就是
ChocolateBoiler被实例化了多次!!
- 一旦被实例化多少次,可能objectA已经把锅炉注满了,objectB又新创建,还以为是空的!
- 那么当务之急就是让ChocolateBoiler变成一个Singleton class
public class ChocolateBoiler { private boolean empty; private boolean boiled; private static ChocolateBoiler uniqueInstance; private ChocolateBoiler() { empty = true; boiled = false; } public static ChocolateBoiler getInstance() { if (uniqueInstance == null) { uniqueInstance = new ChocolateBoiler(); } return uniqueInstance; } public void fill() { if (isEmpty()) { empty = false; boiled = false; // fill the boiler with a milk/cocolate mixture } } // rest of ChocolateBoiler Code }
Singleton Pattern defined
- Singleton模式定义如下
The Sinleton Pattern ensures a class has only one instance, and provides a glob al point of access to it.
Threads are a problem
- 如果我们允许多个thread,同时调用getInstance()函数的话,可能会出现非常严重的问
题. 简言之就是"破坏了只能实例化一个对象"的承诺:
- 首先为了能够使问题更容易实现,让getInstance中间休息一秒
public static ChocolateBoiler getInstance() throws Exception{ if (uniqueInstance == null) { SECONDS.sleep(1); System.out.println("Creating unique instance of Chocolate Boiler, should happen only once!"); uniqueInstance = new ChocolateBoiler(); } System.out.println("Returning instance of Chocolate Boiler"); return uniqueInstance; }
- 创建一个允许getInstance()的thread
package org.hfeng.misc.hfdp.ch5.singleton.notthreadsafe; public class BoilerRunnable implements Runnable { public void run() { try { ChocolateBoiler.getInstance().fill(); ChocolateBoiler.getInstance().boil(); } catch (Exception e) { e.printStackTrace(); } } }
- 我们运行三个thread,应该只创建一次object, 从打印信息来看,我们不小心创建了
三次(每个thread都创建了一个object). singleton的承诺,荡然无存
package org.hfeng.misc.hfdp.ch5.singleton.notthreadsafe; public class MainTest { public static void main(String[] args) { for (int i = 0; i < 3; i++) { new Thread(new BoilerRunnable()).start(); } } } //////////////////////////////////////////////////////////////////////////// // <===================OUTPUT===================> // // Creating unique instance of Chocolate Boiler, should happen only once! // // Returning instance of Chocolate Boiler // // Filling // // Returning instance of Chocolate Boiler // // Creating unique instance of Chocolate Boiler, should happen only once! // // Returning instance of Chocolate Boiler // // Filling // // Returning instance of Chocolate Boiler // // We boiled already! NO MORE Boil should ever happen! // // Creating unique instance of Chocolate Boiler, should happen only once! // // Returning instance of Chocolate Boiler // // Filling // // Returning instance of Chocolate Boiler // // We boiled already! NO MORE Boil should ever happen! // // We boiled already! NO MORE Boil should ever happen! // ////////////////////////////////////////////////////////////////////////////
- 首先为了能够使问题更容易实现,让getInstance中间休息一秒
- 一个显而易见的办法是:
- 让getInstance每次只能有一个thread进入
public static synchronized ChocolateBoiler getInstance() throws Exception{ if (uniqueInstance == null) { SECONDS.sleep(1); System.out.println("Creating unique instance of Chocolate Boiler, should happen only once!"); uniqueInstance = new ChocolateBoiler(); } System.out.println("Returning instance of Chocolate Boiler"); return uniqueInstance; }
- 结果很令人满意
package org.hfeng.misc.hfdp.ch5.singleton.threadsafe; public class MainTest { public static void main(String[] args) { for (int i = 0; i < 3; i++) { new Thread(new BoilerRunnable()).start(); } } } //////////////////////////////////////////////////////////////////////////// // <===================OUTPUT===================> // // Creating unique instance of Chocolate Boiler, should happen only once! // // Returning instance of Chocolate Boiler // // Filling // // Returning instance of Chocolate Boiler // // Returning instance of Chocolate Boiler // // We boiled already! NO MORE Boil should ever happen! // // Returning instance of Chocolate Boiler // // Returning instance of Chocolate Boiler // // Returning instance of Chocolate Boiler // ////////////////////////////////////////////////////////////////////////////
- 让getInstance每次只能有一个thread进入
Can we improve multithreading?
- synchronized已经可以完成任务,但是还是有提高效率的余地:
- 如果getInstance()对你的效率影响没有"很重要", 那么,不必要进行improve. synchronized的方式,一般会降低100倍的速度.所以如果经常调用的话,还是要去优化
- 放弃lazy initialization, 当然了,这样就无法做到我们前面说的"expensive的资源
不需要就不创建". 下面的方法,等于使用了JVM的承诺:一定在某个thread到来之前,
初始化好instance
public class Singleton { private static Singleton uniqueInstance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return uniqueInstance; } }
- 使用"double-check lock"来解决synchronized次数多的问题!(After JDK 1.4).注意!
下面的例子使用了两次的check. 综合下来,我们只有第一个thread来的时候需要synchronized.
还要注意的是double check要同时加上volatile,防止编译器为了性能更改'语句'顺序.
public class Singleton { // volatile is needed here private volatile static Singleton uniqueInstance; private Singleton() {} public static Singleton getInstance() { if (uniqueInstance == null) { // synchronized the Singleton.class, other than this! synchronized (Singleton.class) { ///////////////////////////////////////////////////////////////// // There may be more than one threads waiting outside of the // // synchronized closure, so after the first thread out, the // // second thread go here may still not know that the // // uniqueInstance is already created // ///////////////////////////////////////////////////////////////// if (uniqueInstance == null) { uniqueInstance = new Singleton(); } } } return uniqueInstance; } }
Chapter 6: the Command Pattern: Encapsulating Invocation
Remote Control
- 最近我们又接到了新的任务:
- 我们要设计一个remote control
- 这个remote control有七个'可编程槽', 每个槽都有on off的开关
- remote control还有一个全局的undo开关
- 如何控制这些硬件的函数,都已经有了比如
- 开:函数on()
- 关:函数off()
- 其他函数比如setVolumen(), setInputChannel()也是有的,但是显然我们remote control用不到
- 要支持这些函数,同时还要考虑到以后,可能有更多的硬件(最少有on, off函数),要 能够支持.
- 如果我们没个slot都设计个switch显然是可以解决问题的,比如
// for slot 1 switch(slot1) { case Light: light.on(); case TV: tv.on(); //.. } // for slot 2 switch(slot2) { case Light: light.on(); case TV: tv.on(); //.. }
- 这种设计最主要的问题是,如果增加了一个新的设备比如DVD,那么所有的十四个slot都要 更改代码.
- 这种设计最主要的问题, 是和Light, TV, DVD等耦合的太紧了,解决办法就是:
设计一个Command object, 我们的remote control和这个Command object交谈, 而Command object则负责和它对应的设备(可以是Light, TV, DVD中的一种)交谈
- 这也就是Command Pattern的主要思想: 创建一个中间的object来解耦和
- 对于remoteControl这个例子来说(我们以Light为例子):
- 我们首先创建一个抽象的Command interface
- Remote Control里面就has-one Command Interface (我们把这个叫做Invoker)
- 对于'开'和'关'这两个命令,我们分别要实现两个concreteCommand类
- 这两个concreteCommand类里面要分别has-one 具体的已经存在的Object(这里就是 Light啦), 然后在内部调用已经存在的Object的函数(On或者Off)
- 这样Command Interface和Concrete Command在一起:一边联系了的RemoteControl, 另外一边联系了已经存在了的Object(Light).解除了两者的耦合
- 整体的UML图如下
Figure 18: simple-remote.png
- 下面我们来看看具体的代码:
- Command Interface
package org.hfeng.misc.hfdp.ch6.simpleremote; public interface Command { public void execute(); }
- Command Concrete Class(以LightOn为例子, LightOff相似)
package org.hfeng.misc.hfdp.ch6.simpleremote; public class LightOnCommand implements Command { Light light; public LightOnCommand(Light light) { this.light = light; } public void execute() { light.on(); } }
- 被Command联系的一边: 已经存在的Light类代码(已经被前面的LightOnCommand所包
含了,也就是Composition组合)
package org.hfeng.misc.hfdp.ch6.simpleremote; public class Light { public void on() { System.out.println("Light On"); } public void off() { System.out.println("Light Off"); } }
- 被联系的另外一边,就是remoteControl的代码
package org.hfeng.misc.hfdp.ch6.simpleremote; public class SimpleRemoteControl { Command slot; public SimpleRemoteControl(Command slot) { this.slot = slot; } public SimpleRemoteControl() {} public void setCommand(Command slot) { this.slot = slot; } public void press() { slot.execute(); } }
- 我们最后需要一个Main函数来测试一下
package org.hfeng.misc.hfdp.ch6.simpleremote; public class MainTest { public static void main(String[] args) { Light light = new Light(); LightOnCommand lightOnCommand = new LightOnCommand(light); LightOffCommand lightOffCommand = new LightOffCommand(light); SimpleRemoteControl SRLightOn = new SimpleRemoteControl(); SRLightOn.setCommand(lightOnCommand); SRLightOn.press(); SimpleRemoteControl SRLightOff = new SimpleRemoteControl(); SRLightOff.setCommand(lightOffCommand); SRLightOff.press(); //All other 6 ONs and 6 OFFs SimpleRemoteControl instances //list here ... } } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // Light On // // Light Off // ////////////////////////////////////////////////////
- Command Interface
The Command Pattern defined
- Command Pattern的定义如下
The COmmand Pattern encapsulates a request as an object, thereby letting you parameterize other objects wieth different requests, queue or log requests, and support undoable operations
- 类图如下
Figure 19: command-pattern.png
Time to QA that Undo button!
- 客户的需求和Command pattern的定义都提到了undo, 要我做到undo,我们必须更改
Command interface 增加一个函数undo()
package org.hfeng.misc.hfdp.ch6.remotewithundo; public interface Command { public void execute(); public void undo(); }
- undo的实现很巧妙,就跟execute相反就可以
package org.hfeng.misc.hfdp.ch6.remotewithundo; public class LightOnCommand implements Command { Light light; public LightOnCommand(Light light) { this.light = light; } public void execute() { light.on(); } public void undo() { light.off(); } }
- 我们的Invoker通过一个参数undoCommand始终记录着上次被调动的Command是哪个,然后
一旦调用undoButton的时候,就调用那个Command的undo()
package org.hfeng.misc.hfdp.ch6.remotewithundo; public class RemoteControlWithUndo { Command[] onCommands; Command[] offCommands; Command undoCommand; public RemoteControlWithUndo(){ onCommands = new Command[7]; offCommands = new Command[7]; Command noCommand = new NoCommand(); for (int i = 0; i < 7; i++) { onCommands[i] = noCommand; offCommands[i] = noCommand; } undoCommand = noCommand; } public void setCommand(int slot, Command onCommand, Command offCommand) { onCommands[slot]= onCommand; offCommands[slot] = offCommand; } public void onButtonWasPushed(int slot) { onCommands[slot].execute(); undoCommand = onCommands[slot]; } public void offButtonWasPushed(int slot) { offCommands[slot].execute(); undoCommand = offCommands[slot]; } public void undoButtonWasPushed() { undoCommand.undo(); } }
- 测试例子如下
package org.hfeng.misc.hfdp.ch6.remotewithundo; public class MainTest { public static void main(String[] args) { RemoteControlWithUndo remoteControlWithUndo = new RemoteControlWithUndo(); Light light = new Light(); LightOnCommand lightOnCommand = new LightOnCommand(light); LightOffCommand lightOffCommand = new LightOffCommand(light); remoteControlWithUndo.setCommand(0, lightOnCommand, lightOffCommand); remoteControlWithUndo.onButtonWasPushed(0); remoteControlWithUndo.offButtonWasPushed(0); remoteControlWithUndo.undoButtonWasPushed(); } } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // Light On // // Light Off // // Light On // ////////////////////////////////////////////////////
Chapter 7: the Adapter and Facade Patterns: Being Adaptive
Adapters all around us
- 理解adapter并不难,因为它普遍存在于我们的生活: 比如,在欧洲如果想使用美国的插 头,你得使用一个转接器,这个转接器在英文中的意思就是Adapter
Object oriented adapters
- 看完生活中的例子,我们再来说一个软件开发中的例子:
- Vender为我们提供了一些class的接口让我们来使用, 但是我们已有的代码是无法支
持这些接口的
Figure 20: do-not-match.png
- 我们已有的代码经过测试用例和实际环境的千锤百炼,不能轻易去改,而你又不可能去
更改Vender的代码.那么一个Adapter是一个非常好的选择
Figure 21: adapter-between.png
- Vender为我们提供了一些class的接口让我们来使用, 但是我们已有的代码是无法支
持这些接口的
Adapter Example
- 我们第一章讲过duck的例子, 一个Duck的特点是一能quack, 二能fly
package org.hfeng.misc.hfdp.ch7.ducks; public interface Duck { public void quack(); public void fly(); }
- MallardDuck是一种鸭子,所以继承这个接口就可以
package org.hfeng.misc.hfdp.ch7.ducks; public class MallardDuck implements Duck { public void quack() { System.out.println("Quack"); } public void fly() { System.out.println("Fly"); } }
- 我们有一个如下的接口,需要传入Duck interface
static void testDuck(Duck duck)
- 但是我们有的,确只是Turkey interface
package org.hfeng.misc.hfdp.ch7.ducks; public interface Turkey { public void gobble(); public void fly(); }
- 如果想把Turkey 传入到一个声明类型为Duck的库函数里面,我们需要的就是一个Adapter
package org.hfeng.misc.hfdp.ch7.ducks; public class TurkeyAdapter implements Duck { Turkey turkey; public TurkeyAdapter(Turkey turkey) { this.turkey = turkey; } public void quack() { turkey.gobble(); } public void fly() { for (int i = 0; i < 5; i++) { turkey.fly(); } } }
- 这样我们就可以使用testDuck啦
package org.hfeng.misc.hfdp.ch7.ducks; public class MainTest { public static void main(String[] args) { WildTurkey wildTurkey = new WildTurkey(); Duck turkeyAdapter = new TurkeyAdapter(wildTurkey); testDuck(turkeyAdapter); } static void testDuck(Duck duck) { duck.quack(); duck.fly(); } } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // Gobble gobble // // I'm flying a short distance // // I'm flying a short distance // // I'm flying a short distance // // I'm flying a short distance // // I'm flying a short distance // ////////////////////////////////////////////////////
- 整个例子的类图如下
Figure 22: ducks.png
Adapter Pattern defined
- Adapter Pattern的定义如下
The Adapter Pattern converts the interface of a class into another interface the clients expect. Adapter lets classes work together that couldn't otherwise becasue of incompatible interfaces
- 我们上面的做法使用了interface,其实是adapter的一种(object adapter), adapter
一共有两种:
- object adapter, 也就是我们前面使用的.是使用composition来完成adapt的
Figure 23: object-adapter.png
- class adapter, 是使用多重继承来完成adapt的, 这在java里面无法实现, c++里面
可以. 但是显然不如composition的来的更为灵活
Figure 24: class-adapter.png
- object adapter, 也就是我们前面使用的.是使用composition来完成adapt的
Real world adapters
- 我们来看一个JDK里面存在的使用adapter的例子:
- Java早期的collection(比如Vector, Stack Hashtable等)都implement了一个interface Enumeration. 这个interface有两个函数hasMoreElements()和nextElement()
- 后来,java实现了新的Collection的机制, 引入了一个崭新的和Enumeration类似的 interface: Iterator, 其有三个函数hasNext(),next(),remove()
- 我们这一章讲的是Adapter,所以救世主就肯定是Adapter啦.新的代码都是提供了Iterator
的interface(也就是Target).而我们产品的(久经考验, 不便再改)的代码,可能是Enumeration
的接口,所以,我们要祭出Adapter啦, 类图如下
Figure 25: jdk-adapter.png
- 代码如下, remove不支持怎么办?抛异常呗…
public clas EnumerationIterator implements Iterator { Enumeration enum; public EnumerationIterator(Enumeration enum) { this.enum = enum; } public boolean hasNext() { return enum.hasMoreElements(); } public Object next() { return enum.nextElement(); } public void remove() { throw new UnsupportedOperationException(); } }
There is another pattern in this chapter: Facade
- 我们还要讲述一种比较简单的pattern: Facade
- 之所以说这个pattern简单,是因为它的原理其实就是
Compose多个class在自己内部,然后自己设计函数,在函数内部使用compose的对象
- 说个例子:比如你有一个家庭影院,每次你看的时候都要做很多事情:
- 打开功放
- 打开DVD
- 打开电视机
- …..
- 这每一件事情都是某个object的某个调用函数, 如果这个列表很长的话,对于用户来说, 体验就很不好.我们希望能有个类似"一键开始享受电影"的功能.
- 实现方法,当然就是compose多个object, 然后在自己的oneKeyWatchMovie函数里面调
用这些object
Figure 26: theater-facade.png
Construting your home therater facade
- 下面来看看代码实现:
- 首先是Amplifier类
package org.hfeng.misc.hfdp.ch7.facade; public class Amplifier { String description; public Amplifier(String description) { this.description = description; } public void on() { System.out.println("Amplifier is on"); } }
- 然后是DvdPlayer类 (compose了Amplifer)
package org.hfeng.misc.hfdp.ch7.facade; public class DvdPlayer { private String description; Amplifier amplifier; public DvdPlayer(String description, Amplifier amplifier) { this.description = description; this.amplifier = amplifier; } public void on() { System.out.println("DVD player is on"); } }
- Screen类
package org.hfeng.misc.hfdp.ch7.facade; public class Screen { String description; public Screen(String description) { this.description = description; } public void on() { System.out.println("Screen is on, enjoy the movie"); } }
- Facade类
package org.hfeng.misc.hfdp.ch7.facade; public class HomeTheaterFacade { Amplifier amplifier; DvdPlayer dvdPlayer; Screen screen; public HomeTheaterFacade(Amplifier amplifier, DvdPlayer dvdPlayer, Screen screen) { this.amplifier = amplifier; this.dvdPlayer = dvdPlayer; this.screen = screen; } public void oneKeyWatchMovie() { amplifier.on(); dvdPlayer.on(); screen.on(); } }
- Main代码
package org.hfeng.misc.hfdp.ch7.facade; public class MainTest { public static void main(String[] args) { Amplifier amplifier = new Amplifier("Top Amplifier"); DvdPlayer dvdPlayer = new DvdPlayer("Top DVD Player", amplifier); Screen screen = new Screen("Top Screen"); HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade(amplifier, dvdPlayer, screen); homeTheaterFacade.watchMovie(); } } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // Amplifier is on // // DVD player is on // // Screen is on, enjoy the movie // ////////////////////////////////////////////////////
- 首先是Amplifier类
Facade Pattern defined
- facade pattern非常直接,定义如下
The Facade Pattern provides a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use
- 如果我们从一个更大的视角,我们会看到如下结构:我们隐藏了Facade后面的信息
Figure 27: facade-subsystem.png
The Principle of Least Knowledge
- facade的设计其实蕴含了一条design principle
Principle of Least Knowledge - talk only to your immediate friends
- 这条principle告诉我们,当我们设计一个系统的时候, 要注意和其interact的class的 数目. 这个数目能够小的话,尽量小.这样在系统变动的时候,会造成最小的影响.
Chapter 8: the Template Method Pattern: Encapsulating Algorithms
It's time for some more cafeine
- 咖啡和茶是很多人的生活必备,它们除了都含有咖啡因以外,在制作方法上面也很类似:
- 对于咖啡:
- Boil some water
- Brew coffee in boiling water
- Pour coffee in cup
- Add sugar and milk
- 对于茶:
- Boil some water
- Brew tea in boiling water
- Pour tea in cup
- Add lemon
- 对于咖啡:
Whipping up some coffee and tea classes
- 如果我们把上面的步骤写成代码的话,你会发现,也很相似
- 对于咖啡:
public class Coffee { void prepareRecipe() { boilWater(); brewCoffeeGrinds(); pourInCup(); addSugarAndMilk(); } public void boilWater() { System.out.println("Boling water"); } public void brewCoffeeGrinds() { System.out.println("Dripping Coffee through filter"); } public void pourInCup() { System.out.println("Pouring into cup"); } public void addSugarAndMilk() { System.out.println("Adding Sugar and Milk"); } }
- 对于茶
public class Tea { void prepareRecipe() { boilWater(); steepTeaBag(); pourInCup(); addLemon(); } public void boilWater() { System.out.println("Boiling water"); } public void steepTeaBag() { System.out.println("Steeping the tea"); } public void addLemon() { System.out.println("Adding Lemon"); } public void pourInCup() { System.out.println("Pouring into cup"); } }
- 对于咖啡:
Sir, may I abstract your Coffee, Tea?
- 最直观的感受就是Tea和Coffee里面的boilWater()和pourInCup()是可以提取出来的,
因为它们完全一致. 然后prepareRecipe()就必须设置为abstract函数了,因为其内部
并不是所有函数都能够在base class里面确定.如下图
Figure 28: straightforward-abstract.png
Taking the design further..
- 如果我们更进一步的观察的话会发现:
- brewCoffeeGrinds()和steepTeaBag()是一回事: 都是把原料准备好
- addSugarAndMilk()和addLemon()更是一回事: 都是添加调味料,只是调味料不同而已
- 如果能把这两者抽象成base class里面的相同函数(区别在sub class里面进行指定), 那么我们的prepareReceipe就可以在base class里面实现, 而sub class也就不需要对 其进行扩展了.
- 这种把一个步骤(或者说算法)集合在一个base class函数里面让subclass使用的方法
就是 template method pattern. 类图如下
Figure 29: template-abstract.png
The implementation code
- 首先是base class, 这里面prepareRecipe()是final的,因为我们不想让subclass去改
动我们的算法. 我们能确定的函数比如boilWater()也实现在了base class,不能确定的
函数比如brew(), 我们设计成abstract class,让subclass去实现
package org.hfeng.misc.hfdp.ch8.template.cofeetea; public abstract class CaffeineBeverage { final void prepareRecipe() { boilWater(); brew(); pourInCup(); addCondiments(); } abstract void brew(); abstract void addCondiments(); void boilWater() { System.out.println("Boiling water"); } void pourInCup() { System.out.println("Pouring into cup"); } }
- Coffee函数如下
package org.hfeng.misc.hfdp.ch8.template.cofeetea; public class Coffee extends CaffeineBeverage { @Override public void brew() { System.out.println("Dripping Coffee through filter"); } @Override void addCondiments() { System.out.println("Adding Sugar and Milk"); } }
- Tea函数如下
package org.hfeng.misc.hfdp.ch8.template.cofeetea; public class Tea extends CaffeineBeverage { @Override void brew() { System.out.println("Steeping the tea"); } @Override void addCondiments() { System.out.println("Adding Lemon"); } }
- 测试函数如下
package org.hfeng.misc.hfdp.ch8.template.cofeetea; public class MainTest { public static void main(String[] args) { Tea tea = new Tea(); Coffee coffee = new Coffee(); System.out.println("--------Tea---------------------"); tea.prepareRecipe(); System.out.println("--------Coffee------------------"); coffee.prepareRecipe(); } } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // --------Tea--------------------- // // Boiling water // // Steeping the tea // // Pouring into cup // // Adding Lemon // // --------Coffee------------------ // // Boiling water // // Dripping Coffee through filter // // Pouring into cup // // Adding Sugar and Milk // ////////////////////////////////////////////////////
Meet the Template Method
- 前面我们实现的就是一个template method pattern啦, 总体上来说
The Template Method defines the steps of an algorithm and allows subclasses to provoie the implementation for one or more steps
- 下面一个图详尽的解释了这句话
Figure 30: meet-template-method
Template Method Pattern defined
- 照旧,我们还是对template有一个定义,但远不如上面的图和解释清楚
The Template Method Pattern defines the skeleton of an algorithm in a method, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.
Hooked on Template Method
- template method有一个扩展,叫做"hook",就是在abstract base class里面提供一个
method, 但是这个method内部没有实现(或者是default实现):
- 如果subclass想更改一部分的Algorithm的话,可以选择override这个函数
- 如果subclass不想更改Algorithm的话,那么不用去管这个函数
- 下面就是一个带有hook的template method实现, customerWantsCondiments就是那个
hook
package org.hfeng.misc.hfdp.ch8.template.coffeeteawithhook; public abstract class CaffeineBeverageWithHook { void prepareRecipe() { boilWater(); brew(); pourInCoup(); if (customerWantsCondiments()) { addCondiments(); } } abstract void brew(); abstract void addCondiments(); void boilWater() { System.out.println("Boiling water"); } void pourInCoup() { System.out.println("Pouring into cup"); } boolean customerWantsCondiments() { return true; } }
- 如果你想使用hook的话,就要override函数customerWantsCondiments
package org.hfeng.misc.hfdp.ch8.template.coffeeteawithhook; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class CoffeeWithHook extends CaffeineBeverageWithHook { @Override void brew() { System.out.println("Dripping Coffee through filter"); } @Override void addCondiments() { System.out.println("Adding Sugar and Milk"); } public boolean customerWantsCondiments() { String answer = getUserInput(); if (answer.toLowerCase().startsWith("y")) { return true; } else { return false; } } private String getUserInput() { String answer = null; System.out.println("Would you like milk and sugar with your coffee (y/n)"); BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); try { answer = in.readLine(); } catch (IOException ioe) { System.err.println("IO error trying to read your answer"); } if (answer == null) { return "no"; } return answer; } }
- 我们来看看测试代码
package org.hfeng.misc.hfdp.ch8.template.coffeeteawithhook; public class MainTest { public static void main(String[] args) { TeaWithHook teaWithHook = new TeaWithHook(); CoffeeWithHook coffeeWithHook = new CoffeeWithHook(); System.out.println("--------Tea---------------------"); teaWithHook.prepareRecipe(); System.out.println("--------Coffee------------------"); coffeeWithHook.prepareRecipe(); } } ////////////////////////////////////////////////////////// // <===================OUTPUT===================> // // --------Tea--------------------- // // Boiling water // // Steeping the tea // // Pouring into cup // // Would you like lemon with your coffee (y/n) // // n // // --------Coffee------------------ // // Boiling water // // Dripping Coffee through filter // // Pouring into cup // // Would you like milk and sugar with your coffee (y/n) // // y // // Adding Sugar and Milk // //////////////////////////////////////////////////////////
The Holywood Principls
- Template method with hook的例子,用到了一个新的design pattern: Hollywood Principle
The Hollywood Principle: Don't call us, we'll call you
- 这句话在我们的例子里面是base class (CaffeineBeverageWithHook) 对 subclass
(CoffeeWithHook)说的:
- 首先,你不要调用我,那会产生复杂的调用依赖
- 其次,我给了你在Algorithm里面的hook(你可以选择override这个hook),所以肯定会 调用hook函数的,如果你override了这个函数,那么也就可以说,我调用了你.
Template Methods in the JDK
- JDK中又很多处用到了Template Method with hook, 比如排序. 我们先看下面的例子:
- 首先是排序代码
package org.hfeng.misc.hfdp.ch8.template.sort; import java.util.Arrays; public class MainTest { public static void main(String[] args) { Duck[] ducks = { new Duck("Daffy", 8), new Duck("Dewey", 1), new Duck("Howard", 7), new Duck("Louie", 2), new Duck("Donald", 10), new Duck("Huy", 2) }; System.out.println("Before sorting:"); display(ducks); Arrays.sort(ducks); System.out.println("\nAfter sorting:"); display(ducks); } public static void display(Duck[] ducks) { for (int i = 0; i < ducks.length; i++) { System.out.println(ducks[i]); } } } /////////////////////////////////////////////////////// // <===================OUTPUT===================> // // Before sorting: // // Daffy weights 8 // // Dewey weights 1 // // Howard weights 7 // // Louie weights 2 // // Donald weights 10 // // Huy weights 2 // // // // After sorting: // // Dewey weights 1 // // Louie weights 2 // // Huy weights 2 // // Howard weights 7 // // Daffy weights 8 // // Donald weights 10 // ///////////////////////////////////////////////////////
- 我们知道,如果你想要完成上面的排序, 得让Duck override compareTo.对了,这个
compareTo就是hook method
package org.hfeng.misc.hfdp.ch8.template.sort; public class Duck implements Comparable<Duck> { String name; int weight; public Duck(String name, int weight) { this.name = name; this.weight = weight; } public String toString() { return name + " weights \t" + weight; } public int compareTo(Duck otherDuck) { if (this.weight < otherDuck.weight) { return -1; } else if (this.weight == otherDuck.weight) { return 0; } else { return 1; } } }
- 首先是排序代码
- 例子中的Arrays.sort里面就蕴含了hook:
- 首先Arrays.sort源代码如下
public static void sort(Object[] a) { if (LegacyMergeSort.userRequested) legacyMergeSort(a); else ComparableTimSort.sort(a); }
- 然后legacyMergeSort代码如下
private static void legacyMergeSort(Object[] a) { Object[] aux = a.clone(); mergeSort(aux, a, 0, a.length, 0); }
- mergeSort的代码如下,终于看到了hook compareTo
private static void mergeSort(Object[] src, Object[] dest, int low, int high, int off) { int length = high - low; // Insertion sort on smallest arrays if (length < INSERTIONSORT_THRESHOLD) { for (int i=low; i<high; i++) for (int j=i; j>low && ((Comparable) dest[j-1]).compareTo(dest[j])>0; j--) swap(dest, j, j-1); return; } // Recursively sort halves of dest into src int destLow = low; int destHigh = high; low += off; high += off; int mid = (low + high) >>> 1; mergeSort(dest, src, low, mid, -off); mergeSort(dest, src, mid, high, -off); // If list is already sorted, just copy from src to dest. This is an // optimization that results in faster sorts for nearly ordered lists. if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) { System.arraycopy(src, low, dest, destLow, length); return; } // Merge sorted halves (now in src) into dest for(int i = destLow, p = low, q = mid; i < destHigh; i++) { if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0) dest[i] = src[p++]; else dest[i] = src[q++]; } }
- 首先Arrays.sort源代码如下
- Jdk HttpServlet中的service()也是一个template method的实现
public abstract class HttpServlet extends GenericServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_get_not_supported"); if (protocol.endsWith("1.1")) { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg); } else { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg); } } // default method for doPost doPut... protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); if (ifModifiedSince < lastModified) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if (method.equals(METHOD_POST)) { doPost(req, resp); } else if (method.equals(METHOD_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { doTrace(req,resp); } else { //.. } } }
- 用户的代码总是:
- extends HttpServlet(因为是abstract class所以只能继承了).
- 然后override自己的doGet, doPost…
- 不能去override自己的service(), 因为那个是template method (所以如果service 给设计成final更好)
Chapter 9: the Iterator and Composite Patterns: Well-Managed Collections
- 设计一个类的时候,如果有一系列类型相同的object,一般会考虑使用容器放置, 在java
中有很多这种容器:
- Array
- Stack
- List
- Hashtable
- 容器管理object的时候,会遇到两种特别难以处理的情况:
- 第一种情况: client可以看到我们类内部的实现, 但是client要处理很多个类,但是 这些类里面用了不同的容器. client不想为每一种容器写一套遍历代码!
- 另外一种情况就是,类内部如何放置这些object是你设计的内部细节, 你不想为类外 部所看到. client想要遍历你们,至少你得给几个最基本的函数来让我们完成遍历
- Iterator pattern就是解决上面"两种"困境的办法!
Two resturants merge
- 针对上面第一种情况,我们来说一个例子: 有两个餐馆合并了.但是两个餐馆原来各有
一套菜单系统:
- KFC餐馆使用的是Array[]来存储menuitems
- MCD餐馆使用的是ArrayList来存储menuitems
- 大家的MenuItem定义都是一样的
package org.hfeng.misc.hfdp.ch9.iterator; public class MenuItem { String name; double price; public MenuItem(String name, double price) { this.name = name; this.price = price; } public String toString() { return (name + ", $" + price); } }
- 但是大家都不想更改自己的容器实现(因为那可能要破坏几年如一日正常运转的代码):
- KFC的代码如下
package org.hfeng.misc.hfdp.ch9.iterator; public class KFCMenu { private static final int MAX_ITEMS = 6; private int numberOfItems = 0; private MenuItem[] menuItems; public KFCMenu() { menuItems = new MenuItem[MAX_ITEMS]; addItem("KA", 1.0); addItem("KB", 2.0); addItem("KC", 3.0); } public void addItem(String name, double price) { MenuItem menuItem = new MenuItem(name, price); menuItems[numberOfItems++] = menuItem; } }
- MCD的代码如下
package org.hfeng.misc.hfdp.ch9.iterator; import java.util.ArrayList; public class MCDMenu { ArrayList menuItems; public MCDMenu() { menuItems = new ArrayList(); addItem("MA", 1.0); addItem("MB", 2.0); addItem("MC", 3.0); } public void addItem(String name, double price) { MenuItem menuItem = new MenuItem(name, price); menuItems.add(menuItem); } }
- KFC的代码如下
- 话又说回来,即便是改了容器实现, 但是把容器实现通过如下的代码"暴露"出来,也是
违反面向对象设计要求的. (这也就是上面说的"另外一种情况")
public MenuItem[] getMenuItems() { return menuItems; }
- 解决的办法前面说过了,是Iterator pattern. 其接口样子如下.
ackage org.hfeng.misc.hfdp.ch9.iterator; public interface Iterator { boolean hasNext(); Object next(); }
- Iterator接口概括起来就是说,如果你implements了Iterator,说明你内部必定有容器
你也会告诉我们何时结束,每次当前的内容是什么, 下面就是一个类实现Iterator的例子
package org.hfeng.misc.hfdp.ch9.iterator; public class KFCMenuIterator implements Iterator { private MenuItem[] items; private int position = 0; public KFCMenuIterator(MenuItem[] items) { this.items = items; } public Object next() { MenuItem menuItem = items[position++]; return menuItem; } public boolean hasNext() { if (position >= items.length || items[position] == null) { return false; } else { return true; } } }
- 我们的类其实现在就可以"暴露"一个函数, 这个函数的返回值就是一个Iterator
public Iterator createIterator() { return new KFCMenuIterator(menuItems); }
- 为了让这个接口看起来更专业,我们设计了一个Menu interface, 让我们的类来implements
package org.hfeng.misc.hfdp.ch9.iterator; public interface Menu { public Iterator createIterator(); }
- 现在KFCmenu是这个样子
package org.hfeng.misc.hfdp.ch9.iterator; public class KFCMenu implements Menu{ private static final int MAX_ITEMS = 6; private int numberOfItems = 0; private MenuItem[] menuItems; public KFCMenu() { menuItems = new MenuItem[MAX_ITEMS]; addItem("KA", 1.0); addItem("KB", 2.0); addItem("KC", 3.0); } public void addItem(String name, double price) { MenuItem menuItem = new MenuItem(name, price); menuItems[numberOfItems++] = menuItem; } public Iterator createIterator() { return new KFCMenuIterator(menuItems); } }
- waitress就是使用我们类的client code. 它想遍历容器的话,那么它只需要talk to
Iterator和Menu这两个接口就可以了.
package org.hfeng.misc.hfdp.ch9.iterator; public class Waitress { private Menu kfcMenu; private Menu mcdMenu; public Waitress(Menu kfcMenu, Menu mcdMenu) { this.kfcMenu = kfcMenu; this.mcdMenu = mcdMenu; } public void printMenu() { System.out.println("KFC-------->"); printMenuWithIterator(kfcMenu.createIterator()); System.out.println("MCD-------->"); printMenuWithIterator(mcdMenu.createIterator()); } private void printMenuWithIterator(Iterator iterator) { while (iterator.hasNext()) { MenuItem menuItem = (MenuItem)iterator.next(); System.out.println(menuItem.toString()); } } }
- 测试代码
package org.hfeng.misc.hfdp.ch9.iterator; public class MainTest { public static void main(String[] args) { KFCMenu kfcMenu = new KFCMenu(); MCDMenu mcdMenu = new MCDMenu(); Waitress waitress = new Waitress(kfcMenu, mcdMenu); waitress.printMenu(); } } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // KFC--------> // // KA, $1.0 // // KB, $2.0 // // KC, $3.0 // // MCD--------> // // MA, $1.0 // // MB, $2.0 // // MC, $3.0 // ////////////////////////////////////////////////////
- 总体的类图如下
Figure 31: menu-iterator
Use java's Iterator
- java 提供了内置的Iterator, 并且在巨大多数的容器(主要是Collection接口里面定
义了的iterator())里面实现了返回一个iterator的函数iterator(), 所以比如用
ArrayList这种大路边的容器来实现我们的例子,就很简单了
package org.hfeng.misc.hfdp.ch9.iterator.jdk; import java.util.ArrayList; import java.util.Iterator; public class BreakfastMenu implements Menu{ private ArrayList<MenuItem> menuItems; public BreakfastMenu() { menuItems = new ArrayList<MenuItem>(); menuItems.add(new MenuItem("Pancake", 2.99)); menuItems.add(new MenuItem("Waffles", 3.59)); } public Iterator<MenuItem> createIterator() { // Notice here!! return menuItems.iterator(); } }
- 如果是hashmap这种结构,我们也可以"只返回value的iterator"
package org.hfeng.misc.hfdp.ch9.iterator.jdk; import java.util.HashMap; import java.util.Iterator; public class LunchMenu implements Menu { HashMap<String, MenuItem> map = new HashMap<String, MenuItem>(); public LunchMenu() { addItem("Veggie", 3.99); addItem("Burrito", 4.29); } public void addItem(String name, double price) { MenuItem menuItem = new MenuItem(name, price); map.put(menuItem.getName(), menuItem); } public Iterator<MenuItem> createIterator() { // only the values' iterator are return return map.values().iterator(); } }
- 如果是Array这种结构,那么没有iterator(),那么我们得自己"implements"一个Iterator
然后返回
package org.hfeng.misc.hfdp.ch9.iterator.jdk; import java.util.Iterator; public class DinerMenuIterator implements Iterator<MenuItem> { MenuItem[] list; int position = 0; public DinerMenuIterator(MenuItem[] list) { this.list = list; } public MenuItem next() { return list[position++]; } public boolean hasNext() { if (position >= list.length || list[position] == null) { return false; } else { return true; } } public void remove() { if (position <= 0) { //throw new Exception("You can't remove an item until you've at least one next()"); } if (list[position-1] != null) { for (int i = position - 1; i < (list.length - 1); i++) { list[i] = list[i+1]; } list[list.length] = null; } } }
- 使用这个Iterator的例子
package org.hfeng.misc.hfdp.ch9.iterator.jdk; import java.util.Iterator; public class DinerMenu implements Menu{ static final int MAX_ITEMS = 6; int numberOfItems = 0; MenuItem[] menuItems; public DinerMenu() { menuItems = new MenuItem[MAX_ITEMS]; addItem("Hotdog", 3.05); addItem("Pasta", 3.89); } public void addItem(String name, double price) { MenuItem menuItem = new MenuItem(name, price); menuItems[numberOfItems++] = menuItem; } public Iterator<MenuItem> createIterator() { return new DinerMenuIterator(menuItems); } }
- waitress的代码
package org.hfeng.misc.hfdp.ch9.iterator.jdk; import java.util.Iterator; public class Waitress { private Menu breakfastMenu; private Menu lunchMenu; private Menu dinerMenu; public Waitress(Menu breakfastMenu, Menu lunchMenu, Menu dinerMenu) { this.breakfastMenu = breakfastMenu; this.lunchMenu = lunchMenu; this.dinerMenu = dinerMenu; } public void printMenu() { System.out.println("Breakfast--------"); printMenuWithIterator(breakfastMenu.createIterator()); System.out.println("Lunch------------"); printMenuWithIterator(lunchMenu.createIterator()); System.out.println("Diner------------"); printMenuWithIterator(dinerMenu.createIterator()); } private void printMenuWithIterator(Iterator<MenuItem> iterator) { while (iterator.hasNext()) { MenuItem menuItem = iterator.next(); System.out.print(menuItem.getName() + ", "); System.out.print("$" + menuItem.getPrice()); System.out.println(); } } }
- 测试代码
package org.hfeng.misc.hfdp.ch9.iterator.jdk; public class MainTest { public static void main(String[] args) { BreakfastMenu breakfastMenu = new BreakfastMenu(); LunchMenu lunchMenu = new LunchMenu(); DinerMenu dinerMenu = new DinerMenu(); Waitress waitress = new Waitress(breakfastMenu, lunchMenu, dinerMenu); waitress.printMenu(); } } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // Breakfast-------- // // Pancake, $2.99 // // Waffles, $3.59 // // Lunch------------ // // Veggie, $3.99 // // Burrito, $4.29 // // Diner------------ // // Hotdog, $3.05 // // Pasta, $3.89 // ////////////////////////////////////////////////////
Iterator Pattern defined
- 好了,又到了定义的时间
The Iterator Pattern provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation
- Iterator的广泛应用有很重要的意义, 这意味着你可以写一份的代码但是却能在很多 种的aggregate容器中运行.
- Iterator Pattern另外一个重要的影响是,把"遍历"这个行为的责任,从"aggregate object"转移到iterator object.这样使得"aggregate object"能够更加专注于自己份 内的工作.
- 下面就是Iterator Pattern的类图
Figure 32: iterator.png
Single Responsibility
- 前面说了, 把aggregae object的"遍历责任"拿掉,让它专注于自己的工作.子所以这样
做是为了让某个class"只为一个原因而改动", 如果我们的aggregae object还需要考
虑遍历, 那么:
- 如果collection改变,那么我们的aggregate object要改变
- 如果我们遍历的方法改变, 那么我们的aggregate object也要改变.
- 所以,最好的处理方式是让某一个class只有一个responsibility.
A class should have only one reaseon to change.
Iterators and Collections
- Java中的Iterator能够轻松使用原因在于大多数的容器都实现了java.util.Collection
interface, 这个interface有很多很有用的method
+------------------+ | <<interface>> | | Collection | +------------------+ | add() | | addAll() | | clear() | | contains() | | equals() | | hashCode() | | isEmpty() | | iterator() | | remove() | | removeAll() | | retainAll() | | size() | | toArray() | | | +------------------+
New problem raised
- 现在我们遇到了新问题: 如果我们的菜单下面不仅仅是item,还可能有子菜单,那我们 改如何显示.
- 问题如下(其实在python, ruby中,这都不算事儿,因为array里面的成员不需要类型一
致):
Figure 33: new-problem.png
The Composite Pattern defined
- 在java中,解决的办法就是composite pattern啦, 其定义如下
The Composite Pattern allows you to compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and composition of objects uniformly
- 其实重点就是最后一句, client代码对待一下两者是公平的:
- individual object
- composition of objects
- 如何公平对待这两者呢:继承共同的接口.
Figure 34: composite.png
Designing Menus with Composite
- 让我们来看看上面的Menu的例子的类图
Figure 35: menu-composite.png
- 代码入下:
- 共同的接口(使用了abstract class)
package org.hfeng.misc.hfdp.ch9.composite.menu; public abstract class MenuComponent { public void add(MenuComponent menuComponent) { throw new UnsupportedOperationException(); } public void remove(MenuComponent menuComponent) { throw new UnsupportedOperationException(); } public MenuComponent getChild(int i) { throw new UnsupportedOperationException(); } public String getName() { throw new UnsupportedOperationException(); } public String getDescription() { throw new UnsupportedOperationException(); } public double getPrice() { throw new UnsupportedOperationException(); } public boolean isVegetarian() { throw new UnsupportedOperationException(); } public void print() { throw new UnsupportedOperationException(); } }
- 叶子节点MenuItem
package org.hfeng.misc.hfdp.ch9.composite.menu; public class MenuItem extends MenuComponent { String name; String description; boolean vegetarian; double price; @Override public String getName() { return name; } @Override public String getDescription() { return description; } @Override public boolean isVegetarian() { return vegetarian; } @Override public double getPrice() { return price; } public MenuItem(String name, String description, boolean vegetarian, double price) { this.name = name; this.description = description; this.vegetarian = vegetarian; this.price = price; } public void print() { System.out.println(" " + getName()); if (isVegetarian()) { System.out.print("(v),"); } System.out.println(getPrice()); System.out.println(">>-- " + getDescription()); } }
- composite节点Menu
package org.hfeng.misc.hfdp.ch9.composite.menu; import java.util.ArrayList; import java.util.Iterator; public class Menu extends MenuComponent { ArrayList<MenuComponent> menuComponents = new ArrayList<MenuComponent>(); String name; String description; public Menu(String name, String description) { this.name = name; this.description = description; } public void add(MenuComponent menuComponent) { menuComponents.add(menuComponent); } public void remove(MenuComponent menuComponent) { menuComponents.remove(menuComponent); } public MenuComponent getChild(int i) { return (MenuComponent)menuComponents.get(i); } public String getName() { return name; } public String getDescription() { return description; } public void print() { System.out.print("\n" + getName()); System.out.println(", " + getDescription()); System.out.println("-------------------------"); Iterator<MenuComponent> iterator = menuComponents.iterator(); while (iterator.hasNext()) { iterator.next().print(); } } }
- Client代码Waitress
package org.hfeng.misc.hfdp.ch9.composite.menu; public class Waitress { MenuComponent allMenus; public Waitress(MenuComponent allMenus) { this.allMenus = allMenus; } public void printMenu() { allMenus.print(); } }
- 测试代码
package org.hfeng.misc.hfdp.ch9.composite.menu; public class MainTest { public static void main(String[] args) { MenuComponent breakfastMenu = new Menu("Breakfast", "Breakfast"); MenuComponent lunchMenu = new Menu("Lunch", "Lunch"); MenuComponent dinnerMenu = new Menu("Dinner", "Dinner"); MenuComponent dessertMenu = new Menu("Dessert", "Dessert menu is sub menu of Dinner"); MenuComponent allMenus = new Menu("All", "All menus combined"); allMenus.add(breakfastMenu); allMenus.add(lunchMenu); allMenus.add(dinnerMenu); breakfastMenu.add(new MenuItem( "Pancake", "scrambled eggs", true, 2.99)); breakfastMenu.add(new MenuItem( "Regular Pancake", "eggs, sausage", false, 2.99)); lunchMenu.add(new MenuItem( "HotDo", "hot dog, with cheese", false, 3.05)); dinnerMenu.add(new MenuItem( "Burrito", "A large burrito, with whole pinto beans", true, 4.29)); dessertMenu.add(new MenuItem( "Sorbet", "A scoop of raspberry and a scoop of lime", true, 1.89)); dinnerMenu.add(dessertMenu); Waitress waitress = new Waitress(allMenus); waitress.printMenu(); } }
- 共同的接口(使用了abstract class)
Flashback to Iterator
- 我们前面的代码也用到了Iterator, 但是是Menu或者MenuItem内部使用了这个Iterator 来进行遍历. 但是Iterator pattern的"原意"是要让client代码(也就是这里的waitress) 能够遍历所有内部元素
- 下面就是让这个既使用Composite pattern,也使用Iterator pattern的代码:
- 首先就是让MenuComponent接口有一个createIterator()函数
public abstract class MenuComponent { //... public abstract Iterator createIterator(); }
- MenuItem是没法返回一个iterator的,因为它内部没有容器
public class MenuItem extends MenuComponent { // ... @Override public Iterator createIterator() { return new NullIterator(); } }
- NullIterator 类的定义如下(意思是永远不会有下一个的iterator)
package org.hfeng.misc.hfdp.ch9.composite.menuiterator; import java.util.Iterator; public class NullIterator implements Iterator<MenuComponent> { public MenuComponent next() { return null; } public boolean hasNext() { return false; } public void remove() { throw new UnsupportedOperationException(); } }
- 和MenuItem对应的是Menu, 它也继承MenuComponent,但是它含有容器,所以它要返回
一个"真的iterator"
public class Menu extends MenuComponent { ArrayList<MenuComponent> menuComponents = new ArrayList<MenuComponent>(); String name; String description; public Menu(String name, String description) { this.name = name; this.description = description; } //... @Override public Iterator createIterator() { return new CompositeIterator(menuComponents.iterator()); } }
- 返回的CompositeIterator是我们新创建的,要应付Menu里面还有Menu这种逻辑的Iterator
package org.hfeng.misc.hfdp.ch9.composite.menuiterator; import java.util.*; public class CompositeIterator implements Iterator<MenuComponent> { Stack<Iterator<MenuComponent>> stack = new Stack<Iterator<MenuComponent>>(); public CompositeIterator(Iterator iterator) { stack.push(iterator); } public MenuComponent next() { if (hasNext()) { Iterator<MenuComponent> iterator = stack.peek(); MenuComponent component = (MenuComponent)iterator.next(); if (component instanceof Menu) { stack.push(component.createIterator()); } return component; } else { return null; } } public boolean hasNext() { if (stack.empty()) { return false; } else { Iterator<MenuComponent> iterator = stack.peek(); if (!iterator.hasNext()) { stack.pop(); return hasNext(); } else { return true; } } } public void remove() { throw new UnsupportedOperationException(); } }
- 现在我们的waitress就可以遍历容器里面的内容,然后判断是不是蔬菜,是的话,才会
打印.
package org.hfeng.misc.hfdp.ch9.composite.menuiterator; import java.util.Iterator; public class Waitress { MenuComponent allMenus; public Waitress(MenuComponent allMenus) { this.allMenus = allMenus; } public void printMenu() { allMenus.print(); } public void printVegetarianMenu() { Iterator<MenuComponent> iterator = allMenus.createIterator(); System.out.println("\nVEGETARIAN MENU\n"); while (iterator.hasNext()) { MenuComponent menuComponent = iterator.next(); try { if (menuComponent.isVegetarian()) { menuComponent.print(); } } catch (UnsupportedOperationException e) { // do nothing here, we just igore, as Menu object does throw // UnsupportedOperationException when calling isVegetarian } } } }
- 测试代码如下
package org.hfeng.misc.hfdp.ch9.composite.menuiterator; public class MainTest { public static void main(String[] args) { MenuComponent breakfastMenu = new Menu("Breakfast", "Breakfast"); MenuComponent lunchMenu = new Menu("Lunch", "Lunch"); MenuComponent dinnerMenu = new Menu("Dinner", "Dinner"); MenuComponent dessertMenu = new Menu("Dessert", "Dessert menu is sub menu of Dinner"); MenuComponent allMenus = new Menu("All", "All menus combined"); allMenus.add(breakfastMenu); allMenus.add(lunchMenu); allMenus.add(dinnerMenu); breakfastMenu.add(new MenuItem( "Pancake", "scrambled eggs", true, 2.99)); breakfastMenu.add(new MenuItem( "Regular Pancake", "eggs, sausage", false, 2.99)); lunchMenu.add(new MenuItem( "HotDo", "hot dog, with cheese", false, 3.05)); dinnerMenu.add(new MenuItem( "Burrito", "A large burrito, with whole pinto beans", true, 4.29)); dessertMenu.add(new MenuItem( "Sorbet", "A scoop of raspberry and a scoop of lime", true, 1.89)); dinnerMenu.add(dessertMenu); Waitress waitress = new Waitress(allMenus); waitress.printVegetarianMenu(); } } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // VEGETARIAN MENU // // // // Pancake // // (v),2.99 // // >>-- scrambled eggs // // Burrito // // (v),4.29 // // >>-- A large burrito, with whole pinto beans // // Sorbet // // (v),1.89 // // >>-- A scoop of raspberry and a scoop of lime // // Sorbet // // (v),1.89 // // >>-- A scoop of raspberry and a scoop of lime // ////////////////////////////////////////////////////
- 首先就是让MenuComponent接口有一个createIterator()函数
Chapter 10: the State Pattern: The State of Things
- 我们有个口香糖球(Gumball)售卖机, 然后有他们的state转换图如下, 希望能够有个程
序实现
Figure 36: gumball-state.png
- 如果你没有学过设计模式,那么下面的解法是很容易直接的映入脑海的.
package org.hfeng.misc.hfdp.ch10.state; public class GumballMachine { final static int SOLD_OUT = 0; final static int NO_QUARTER = 1; final static int HAS_QUARTER = 2; final static int SOLD = 3; int state = SOLD_OUT; int count = 0; public GumballMachine(int count) { this.count = count; if (count > 0) { state = NO_QUARTER; } } public void insertQuarter() { if (state == HAS_QUARTER) { System.out.println("You can't insert another quater"); } else if (state == NO_QUARTER) { state = HAS_QUARTER; System.out.println("You inserted a quater"); } else if (state == SOLD_OUT) { System.out.println("You can't insert a quater, the machine is sold out"); } else if (state == SOLD) { System.out.println("Please wait, we're already giving you a gumball"); } } public void ejectQuarter() { if (state == HAS_QUARTER) { System.out.println("Quarter returned"); state = NO_QUARTER; } else if (state == NO_QUARTER) { System.out.println("You haven't inserted a quarter"); } else if (state == SOLD) { System.out.println("Sorry, you already turned the crank"); } else if (state == SOLD_OUT) { System.out.println("You can't eject, you haven't inserted a quarter yet"); } } public void turnCrank() { if (state == SOLD) { System.out.println("Turning twice doesn't get you another gumball!"); } else if (state == NO_QUARTER) { System.out.println("You turned but there's no quarter"); } else if (state == SOLD_OUT) { System.out.println("You turned, but there are no gumballs"); } else if (state == HAS_QUARTER) { System.out.println("You turned..."); state = SOLD; dispense(); } } public void dispense() { if (state == SOLD) { System.out.println("A gumball comes rolling out the slot"); count = count - 1; if (count == 0) { System.out.println("Oops, out of gumballs!"); state = SOLD_OUT; } else { state = NO_QUARTER; } } else if (state == NO_QUARTER) { System.out.println("You need to pay first"); } else if (state == SOLD_OUT) { System.out.println("No gumball dispensed"); } else if (state == HAS_QUARTER) { System.out.println("No gumball dispensed"); } } public void refill(int numGumBalls) { this.count = numGumBalls; state = NO_QUARTER; } public String toString() { StringBuffer result = new StringBuffer(); result.append("\nMighty Gumball, Inc."); result.append("\nJava-enabled Standing Gumball Model #2004\n"); result.append("Inventory: " + count + " gumball"); if (count != 1) { result.append("s"); } result.append("\nMachine is "); if (state == SOLD_OUT) { result.append("sold out"); } else if (state == NO_QUARTER) { result.append("waiting for quarter"); } else if (state == HAS_QUARTER) { result.append("waiting for turn of crank"); } else if (state == SOLD) { result.append("delivering a gumball"); } result.append("\n"); return result.toString(); } }
The messy State of things
- 这种设计最大的问题,在于我们如果有了CHANGE, 比如,增加了一种新的玩法: 有10%的
可能性玩家可以花25美分得到两个球! state图如下
Figure 37: gumball-winner-state.png
- 如果还是上面的解法的话,面对这种改变就显得很无力, 因为要有很多的"inside"的改
动,比如我们要设置新的static final 的state
public class GumballMachine { final static int SOLD_OUT = 0; //... //new state final static int WINNER = 4; //... }
The new design
- 解决的办法自然就是我们这一章的重点, state pattern. 所谓state pattern在我看来,
就是把final static int的每一个部分都拆出来,拆成一个class. 这些class都obey一
个State interface就可以了.
Figure 38: winner.png
Implementing our state class
- 首先是最重要的state interface
package org.hfeng.misc.hfdp.ch10.state.winner; public interface State { public void insertQuarter(); public void ejectQuarter(); public void turnCrank(); public void dispense(); }
- 老的state都implements state interface, 比如最开始的状态:NoQuarterState
package org.hfeng.misc.hfdp.ch10.state.winner; public class NoQuarterState implements State { GumballMachine gumballMachine; public NoQuarterState(GumballMachine gumballMachine) { this.gumballMachine = gumballMachine; } public void insertQuarter() { System.out.println("You inserted a quarter"); gumballMachine.setState(gumballMachine.getHasQuarterState()); } public void ejectQuarter() { System.out.println("You haven't inserted a quarter"); } public void turnCrank() { System.out.println("You turned, but there's no quarter"); } public void dispense() { System.out.println("You need to pay first"); } public String toString() { return "waiting for quarter"; } }
- 我们需求有了改变,需要新加入一个状态的时候,直接创建一个新的类implements state
interface
package org.hfeng.misc.hfdp.ch10.state.winner; public class WinnerState implements State { GumballMachine gumballMachine; public WinnerState(GumballMachine gumballMachine) { this.gumballMachine = gumballMachine; } public void insertQuarter() { System.out.println("Please wait, we're already giving you a Gumball"); } public void ejectQuarter() { System.out.println("Please wait, we're already giving you a Gumball"); } public void turnCrank() { System.out.println("Turning again doesn't get you another gumball!"); } public void dispense() { System.out.println("YOU'RE A WINNER! You get two gumballs for your quarter"); gumballMachine.releaseBall(); if (gumballMachine.getCount() == 0) { gumballMachine.setState(gumballMachine.getSoldOutState()); } else { gumballMachine.releaseBall(); if (gumballMachine.getCount() > 0) { gumballMachine.setState(gumballMachine.getNoQuarterState()); } else { System.out.println("Oops, out of gumballs!"); gumballMachine.setState(gumballMachine.getSoldOutState()); } } } public String toString() { return "dispensing two gumballs for your quarter, because YOU'RE A WINNER!"; } }
- 我们的GumballMachine 是使用这些state class的class
package org.hfeng.misc.hfdp.ch10.state.winner; public class GumballMachine { State soldOutState; State noQuarterState; State hasQuarterState; State soldState; State winnerState; State state = soldState; int count = 0; public State getSoldState() { return soldState; } public GumballMachine(int numberCumballs) { soldOutState = new SoldOutState(this); noQuarterState = new NoQuarterState(this); hasQuarterState = new HasQuarterState(this); soldState = new SoldState(this); winnerState = new WinnerState(this); this.count = numberCumballs; if (numberCumballs > 0) { state = noQuarterState; } } public void turnCrank() { state.turnCrank(); state.dispense(); } public void releaseBall() { System.out.println("A gumball comes rolling out the slot..."); if (count != 0) { count = count - 1; } } public void setState(State state) { this.state = state; } public State getSoldOutState() { return soldOutState; } public State getNoQuarterState() { return noQuarterState; } public State getHasQuarterState() { return hasQuarterState; } public State getWinnerState() { return winnerState; } public State getState() { return state; } public int getCount() { return count; } public void insertQuarter() { state.insertQuarter(); } public void ejectQuarter() { state.ejectQuarter(); } public String toString() { StringBuffer result = new StringBuffer(); result.append("\nMighty Gumball, Inc."); result.append("\nJava-enabled Standing Gumball Model #2004"); result.append("\nInventory: " + count + " gumball"); if (count != 1) { result.append("s"); } result.append("\n"); result.append("Machine is " + state + "\n"); return result.toString(); } }
- 测试代码如下
package org.hfeng.misc.hfdp.ch10.state.winner; public class MainTest { public static void main(String[] args) { GumballMachine gumballMachine = new GumballMachine(10); System.out.println(gumballMachine); gumballMachine.insertQuarter(); gumballMachine.turnCrank(); gumballMachine.insertQuarter(); gumballMachine.turnCrank(); } } ///////////////////////////////////////////////////////////// // <===================POSSIBLE-OUTPUT===================> // // Mighty Gumball, Inc. // // Java-enabled Standing Gumball Model #2004 // // Inventory: 10 gumballs // // Machine is waiting for quarter // // // // You inserted a quarter // // You turned... // // YOU'RE A WINNER! You get two gumballs for your quarter // // A gumball comes rolling out the slot... // // A gumball comes rolling out the slot... // // You inserted a quarter // // You turned... // // A gumball comes rolling out the slot... // /////////////////////////////////////////////////////////////
The State Pattern defined
- state pattern的文字定义如下
The State Pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class.
Chapter 11: the Proxy Pattern: Controlling Object Access
New requirement
- GumballMachine的老板希望能给他出具一份公司所有产品的summary报告.
- 我们最直观的感受就是改动当前的GumballMachine给它加一个location的新的成员变量
用来区分"世界各地的,不同的"GumballMachine
Modified src/main/java/org/hfeng/misc/hfdp/ch11/proxy/winner/GumballMachine.java diff --git a/src/main/java/org/hfeng/misc/hfdp/ch11/proxy/winner/GumballMachine.java b/src/main/java/org/hfeng/misc/hfdp/ch11/proxy/winner/GumballMachine.java index a9b28bd..89c72fd 100644 --- a/src/main/java/org/hfeng/misc/hfdp/ch11/proxy/winner/GumballMachine.java +++ b/src/main/java/org/hfeng/misc/hfdp/ch11/proxy/winner/GumballMachine.java @@ -9,11 +9,17 @@ public class GumballMachine { State state = soldState; int count = 0; + String location; + + public String getLocation() { + return location; + } + public State getSoldState() { return soldState; } - public GumballMachine(int numberGumballs) { + public GumballMachine(String location, int numberGumballs) { soldOutState = new SoldOutState(this); noQuarterState = new NoQuarterState(this); hasQuarterState = new HasQuarterState(this); @@ -24,6 +30,7 @@ public class GumballMachine { if (numberGumballs > 0) { state = noQuarterState; } + this.location = location; }
- 然后我们设计一个新的报告用的类GumballMonitor
package org.hfeng.misc.hfdp.ch11.proxy.monitor; public class GumballMonitor { GumballMachine machine; public GumballMonitor(GumballMachine machine) { this.machine = machine; } public void report() { System.out.println("Gumball Machine: " + machine.getLocation()); System.out.println("Current inventory: " + machine.getCount() + " gumballs"); System.out.println("Current state: " + machine.getState()); } }
- 测试用例如下
package org.hfeng.misc.hfdp.ch11.proxy.monitor; public class MainTest { public static void main(String[] args) { int count = 0; if (args.length < 2) { System.out.println("Usage GumballMachine <Location> <Number>"); System.exit(1); } count = Integer.parseInt(args[1]); GumballMachine gumballMachine = new GumballMachine(args[0], count); GumballMonitor monitor = new GumballMonitor(gumballMachine); monitor.report(); } } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // > java MainTest Seattle 112 // // Gumball Machine: Seattle // // Current inventory: 112 gumballs // // Current state: waiting for quarter // ////////////////////////////////////////////////////
Misunderstanding
- 上面的做法是能解决GumballMachine在本地时候的情况.但是对方CEO的想法是"remotely"
的监控GumballMachine.也就是说
GumBallMonitor 和 GumBallMachine在两台不同的机器上面(JVM也就不同, 所以, 你不可以让GumBallMonitor compose 一个GumBallMachine了.
- 解决的办法就是proxy pattern啦
- 所谓proxy pattern, 其实就是你不在compose"另外一个jvm"里面的对象, 而是compose
一个proxy对象, 让这个对象去和"另外一个jvm"里面的对象通信.如下图
Figure 39: proxy.png
- 在java中,两个jvm(两个机器)之间的通信,就是通过RMI.
The Proxy Pattern defined
- 定义如下
The Proxy Pattern provides a surrogate or placeholder for another object to control access to it.