UP | HOME

head-first-design-pattern

Table of Contents

Chapter 1: Intro to Design Patterns

It started with a simple SimUDuck app

  • Joe公司的要设计一款有很多种类型鸭子的游戏.所以很自然的,他们首先创建了一个superclass SimUDuck

    duck-super.png

    Figure 1: duck-super.png

  • 从上例我们可以看到:
    • Duck是superclass
    • 在其内部就实现了quack()和swim()
    • 而把display()设计成abstract的, 因为所有的子类型display出来都是不同的.

But now we need the ducks to FLY

  • 后来公司面对激烈的市场竞争,决定增加自己游戏里面鸭子的"能力",原来只会游泳和叫 现在我们要这个鸭子会飞!
  • 最简单的实现方法就是在superclass里面加上一个fly() 函数,这下多有的subclass就 都会飞啦!

    duck-add-fly.png

    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这个接口

    duck-interface.png

    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们

    duck-behavior.png

    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

  • 好了,我们从从下图就可以看到整个的类图全貌.

    duck-summary.png

    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的类图

    observer.png

    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

  • 整个系统的设计类图如下

    weather-data.png

    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      //
      ////////////////////////////////////////////////////////
      

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 //
      //////////////////////////////////////////////////////////
      
  • 上面的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:
      1. 这没有必要,因为我们必须继承Observerable, 而我们如果已经有一套成熟代码 的情况下想"获取Observable这种能力",那么我们implement接口就好了(脑补 Runnable), 但是设计成class就没办法了
      2. 你甚至不能提供自己的observable实现,比如多线程安全版本的
    • Observable 的关键函数的protected的: 如果你看了代码你会发现Observable最关键的函数setChanged()是protected,这就 说明你必须得subclass才能调用,连composition这条路都堵上了.

Chapter 3: the Decorator Pattern Decorating Objects

Welcome to Starbuzz Coffee

  • Starbuzz咖啡是一个非常出名的品牌,他们店内有非常多的饮料,具体类图如下Beverage 是一个abstract class

    first-beverage.png

    Figure 8: first-beverage.png

  • 但是你想到了没有,如果在原有的饮料基础上,新加一些调料的话,会不停的增加新的类, 然后就会出现所谓的"class explosion"(很多没必要的类都会出现):

    explosion-beverage.png

    Figure 9: explosion-beverage.png

  • 如果不想出现"class explosion",我们也在abstract class里面设置一些instance variable, 然后其他class继承了这个superclass就有这些"变化"了

    instance-beverage.png

    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的类图如下

    decorate-component.png

    Figure 11: decorate-component.png

  • 什么是decorator pattern呢?就是说:
    • 我们有个基本的咖啡DarkRoast
    • 第一步"装饰": Mocha
    • 第二步"装饰": Whip
    • 这样一来调用cost()的时候,就可以根据装饰的过程来计算最后的价格
  • 这个过程听起来是有些难以理解,看看类图:

    decorate-beverage.png

    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     //
      ////////////////////////////////////////////////////
      

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的类图

    simple-factory.png

    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的类图

    factory-method.png

    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类型的"

    very-dependent-pizzastore.png

    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:
      1. high-level依赖low-level的接口
      2. 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"

    apply-principle-pizzastore.png

    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;
          }
      }
      
  • 最后我们来看看Abstract Factory的类图,因为我们的例子中concrete Pizza是在一个 factory method中实现的,所以不是太明显,而在普通的Abstrct Factory中,其实这就是 一个Client Class

    abstract-factory.png

    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每次只能有一个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                                 //
      ////////////////////////////////////////////////////////////////////////////
      

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开关
    • 如何控制这些硬件的函数,都已经有了比如
      1. 开:函数on()
      2. 关:函数off()
      3. 其他函数比如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图如下

    simple-remote.png

    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                                      //
      ////////////////////////////////////////////////////
      

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
    
  • 类图如下

    command-pattern.png

    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的接口让我们来使用, 但是我们已有的代码是无法支 持这些接口的

      do-not-match.png

      Figure 20: do-not-match.png

    • 我们已有的代码经过测试用例和实际环境的千锤百炼,不能轻易去改,而你又不可能去 更改Vender的代码.那么一个Adapter是一个非常好的选择

      adapter-between.png

      Figure 21: adapter-between.png

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                    //
    ////////////////////////////////////////////////////
    
  • 整个例子的类图如下

    ducks.png

    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的

      object-adapter.png

      Figure 23: object-adapter.png

    • class adapter, 是使用多重继承来完成adapt的, 这在java里面无法实现, c++里面 可以. 但是显然不如composition的来的更为灵活

      class-adapter.png

      Figure 24: class-adapter.png

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啦, 类图如下

    jdk-adapter.png

    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

    theater-facade.png

    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                  //
      ////////////////////////////////////////////////////
      

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后面的信息

    facade-subsystem.png

    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

  • 咖啡和茶是很多人的生活必备,它们除了都含有咖啡因以外,在制作方法上面也很类似:
    • 对于咖啡:
      1. Boil some water
      2. Brew coffee in boiling water
      3. Pour coffee in cup
      4. Add sugar and milk
    • 对于茶:
      1. Boil some water
      2. Brew tea in boiling water
      3. Pour tea in cup
      4. 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里面确定.如下图

    straightforward-abstract.png

    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. 类图如下

    template-abstract.png

    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
    
  • 下面一个图详尽的解释了这句话

    meet-template-method.png

    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++];
          }
      }
      
  • 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);
          }
      }
      
  • 话又说回来,即便是改了容器实现, 但是把容器实现通过如下的代码"暴露"出来,也是 违反面向对象设计要求的. (这也就是上面说的"另外一种情况")
    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                                       //
    ////////////////////////////////////////////////////
    
  • 总体的类图如下

    menu-iterator.png

    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的类图

    iterator.png

    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里面的成员不需要类型一 致):

    new-problem.png

    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
  • 如何公平对待这两者呢:继承共同的接口.

    composite.png

    Figure 34: composite.png

Designing Menus with Composite

  • 让我们来看看上面的Menu的例子的类图

    menu-composite.png

    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();
          }
      }
      

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  //
      ////////////////////////////////////////////////////
      

Chapter 10: the State Pattern: The State of Things

  • 我们有个口香糖球(Gumball)售卖机, 然后有他们的state转换图如下, 希望能够有个程 序实现

    gumball-state.png

    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图如下

    gumball-winner-state.png

    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就可以了.

    winner.png

    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"里面的对象通信.如下图

    proxy.png

    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.