天天看点

Design Pattern学习笔记之组合模式(Compound Patterns)Design Pattern学习笔记之组合模式(Compound Patterns)

Design Pattern学习笔记之组合模式(Compound Patterns)

1.    引子--Whois?

我们之前介绍过很多设计模式,我们力图从各个角度辨析各种模式的异同,找到某种模式存在的理由。简言之,我们尽力识别模式的不同,在解决具体问题时,一般使用单一模式解决问题。其实之前介绍的例子,都是为了帮助理解设计模式,在现实的世界里,有更多更复杂的问题要处理,很多非常好的OO设计都是多种设计模式组合使用的结果。

在本节,我们照例会按照具体的示例来说明组合模式(即多种设计模式组合使用)所能显示的威力。

2.    问题来了—模拟鸭塘游戏

大家应该还记得我们的设计模式之旅就是从模拟鸭塘游戏开始的,在整个设计模式要介绍完成时,又回到了这里,只是我们再不是懵懂少年,我们的设计工具箱里已经有了十几种设计模式和OO设计准则,应该对设计模式的应用更得心应手。

这一次我们关注各种鸭子的叫声,先定义一个接口:

public interface Quackable {

        public void quack();

}

不同的鸭子,有不同的叫声,我们来看看部分实现:

public class DecoyDuck implements Quackable {

    public void quack() {

       System.out.println("<<Silence >>");

    }

}

public class MallardDuck implements Quackable {

    public void quack() {

       System.out.println("Quack");

    }

}

public class RubberDuck implements Quackable {

    public void quack() {

       System.out.println("Squeak");

    }

}

再看看模拟鸭塘(只模拟叫声):

public class DuckSimulator {

    public static void main(String[] args) {

       DuckSimulator simulator = new DuckSimulator();

       simulator.simulate();

    }

    void simulate() {

       Quackable mallardDuck = new MallardDuck();

       Quackable redheadDuck = new RedheadDuck();

       Quackable duckCall = new DuckCall();

       Quackable rubberDuck = new RubberDuck();

       System.out.println("\nDuckSimulator");

       simulate(mallardDuck);

       simulate(redheadDuck);

       simulate(duckCall);

       simulate(rubberDuck);

    }

    void simulate(Quackable duck) {

       duck.quack();

    }

}

1.  增加鹅—适配器模式

鹅跟鸭子有很多相似之处,我们想把鹅也加入到模拟鸭塘游戏中,来看一下鹅的实现:

public class Goose {

    public void honk() {

       System.out.println("Honk");

    }

}

显然鹅叫的接口跟鸭子的接口不同,我们使用适配器模式来处理这种不同,使得鹅也能在模拟鸭塘游戏中使用:

public class GooseAdapter implements Quackable {

    Goose goose;

    public GooseAdapter(Goose goose) {

       this.goose = goose;

    }

    public void quack() {

       goose.honk();

    }

    public String toString() {

       return "Goosepretending to be a Duck";

    }

}

很简单,创建对应的适配器类,将鸭子叫的接口转换为鹅叫的接口。

2.  计算鸭群鸣叫次数—装饰模式

生物学家需要统计某个鸭群的叫声,来研究环境变化对鸭塘生态的影响。使用装饰模式,在装饰类中为每一个鸭子增加计算叫声的新行为。

public class QuackCounter implements Quackable {

    Quackable duck;

    static int numberOfQuacks;

    public QuackCounter (Quackable duck) {

       this.duck = duck;

    }

    public void quack() {

       duck.quack();

       numberOfQuacks++;

    }

    public static int getQuacks() {

       return numberOfQuacks;

    }

    public String toString() {

       return duck.toString();

    }

}

3.  统一创建不同种类的鸭子—抽象工厂模式

来看看应用装饰模式后,现在的鸭塘模拟程序:

public class DuckSimulator {

    public static void main(String[] args) {

       DuckSimulator simulator = new DuckSimulator();

       simulator.simulate();

    }

    void simulate() {

       Quackable mallardDuck = new QuackCounter(new MallardDuck());

       Quackable redheadDuck = new QuackCounter(new RedheadDuck());

       Quackable duckCall = new QuackCounter(new DuckCall());

       Quackable rubberDuck = new QuackCounter(new RubberDuck());

       Quackable gooseDuck = new GooseAdapter(new Goose());

       System.out.println("\nDuckSimulator: With Decorator");

       simulate(mallardDuck);

       simulate(redheadDuck);

       simulate(duckCall);

       simulate(rubberDuck);

       simulate(gooseDuck);

       System.out.println("The ducksquacked " +

                         QuackCounter.getQuacks() + " times");

    }

    void simulate(Quackable duck) {

       duck.quack();

    }

}

可以看到为了能计算鸭子叫声,我们在创建每一种鸭子时都需要记着创建装饰类QuackCounter,如果不小心忘记了为某种鸭子创建装饰类,这种鸭子的鸣叫声就不会统计进来,我们应用抽象工厂模式将创建一系列鸭子的工作放在一个地方定义。

public abstract class AbstractDuckFactory {

    public abstract Quackable createMallardDuck();

    public abstract Quackable createRedheadDuck();

    public abstract Quackable createDuckCall();

    public abstract Quackable createRubberDuck();

}

public class CountingDuckFactory extends AbstractDuckFactory {

    public Quackable createMallardDuck() {

       return new QuackCounter(new MallardDuck());

    }

    public Quackable createRedheadDuck() {

       return new QuackCounter(new RedheadDuck());

    }

    public Quackable createDuckCall() {

       return new QuackCounter(new DuckCall());

    }

    public Quackable createRubberDuck() {

       return new QuackCounter(new RubberDuck());

    }

}

public class DuckSimulator {

    public static void main(String[] args) {

       DuckSimulator simulator = new DuckSimulator();

       AbstractDuckFactory duckFactory = new CountingDuckFactory();

       simulator.simulate(duckFactory);

    }

    void simulate(AbstractDuckFactory duckFactory) {

       Quackable mallardDuck = duckFactory.createMallardDuck();

       Quackable redheadDuck = duckFactory.createRedheadDuck();

       Quackable duckCall = duckFactory.createDuckCall();

       Quackable rubberDuck = duckFactory.createRubberDuck();

       Quackable gooseDuck = new GooseAdapter(new Goose());

       System.out.println("\nDuckSimulator: With Abstract Factory");

       simulate(mallardDuck);

       simulate(redheadDuck);

       simulate(duckCall);

       simulate(rubberDuck);

       simulate(gooseDuck);

       System.out.println("The ducksquacked " +

                         QuackCounter.getQuacks() +

                          " times");

    }

    void simulate(Quackable duck) {

       duck.quack();

    }

}

4.  管理鸭群—复合模式

目前的鸭子都是单个的,不好管理,想把同一类的鸭子放到一起管理。显然,应该使用复合模式来处理整体-局部关系的对象。

public class Flock implements Quackable {

    ArrayList quackers = new ArrayList();

    public void add(Quackable quacker) {

       quackers.add(quacker);

    }

    public void quack() {

       Iterator iterator = quackers.iterator();

       while (iterator.hasNext()) {

           Quackable quacker = (Quackable)iterator.next();

           quacker.quack();

       }

    }

    public String toString() {

       return "Flock of Quackers";

    }

}

void simulate(AbstractDuckFactory duckFactory) {

       Quackable redheadDuck = duckFactory.createRedheadDuck();

       Quackable duckCall = duckFactory.createDuckCall();

       Quackable rubberDuck = duckFactory.createRubberDuck();

       Quackable gooseDuck = new GooseAdapter(new Goose());

       System.out.println("\nDuckSimulator: With Composite - Flocks");

       Flock flockOfDucks = new Flock();

       flockOfDucks.add(redheadDuck);

       flockOfDucks.add(duckCall);

       flockOfDucks.add(rubberDuck);

       flockOfDucks.add(gooseDuck);

       Flock flockOfMallards = new Flock();

       Quackable mallardOne = duckFactory.createMallardDuck();

       Quackable mallardTwo = duckFactory.createMallardDuck();

       Quackable mallardThree = duckFactory.createMallardDuck();

       Quackable mallardFour = duckFactory.createMallardDuck();

       flockOfMallards.add(mallardOne);

       flockOfMallards.add(mallardTwo);

       flockOfMallards.add(mallardThree);

       flockOfMallards.add(mallardFour);

       flockOfDucks.add(flockOfMallards);

       System.out.println("\nDuckSimulator: Whole Flock Simulation");

       simulate(flockOfDucks);

       System.out.println("\nDuckSimulator: Mallard Flock Simulation");

       simulate(flockOfMallards);

       System.out.println("\nThe ducksquacked " +

                         QuackCounter.getQuacks() +

                          " times");

}

5.  监控每只鸭子的叫声—观察者模式

需要监控每只鸭子的叫声,很显然可以应用观察者模式,将每只鸭子作为subject,将监控器作为observer,在每次鸭子叫时,将消息通知到每一个observer(调用观察者的更新方法)。

public interface QuackObservable {

    public void registerObserver(Observer observer);

    public void notifyObservers();

}

public class Observable implements QuackObservable {

    ArrayList observers = new ArrayList();

    QuackObservable duck;

    public Observable(QuackObservable duck) {

       this.duck = duck;

    }

    public void registerObserver(Observer observer) {

       observers.add(observer);

    }

    public void notifyObservers() {

       Iterator iterator = observers.iterator();

       while (iterator.hasNext()) {

           Observer observer = (Observer)iterator.next();

           observer.update(duck);

       }

    }

    public Iterator getObservers() {

       return observers.iterator();

    }

}

public class DecoyDuck implements Quackable {

    Observable observable;

    public DecoyDuck() {

       observable = new Observable(this);

    }

    public void quack() {

       System.out.println("<<Silence >>");

       notifyObservers();

    }

    public void registerObserver(Observer observer) {

       observable.registerObserver(observer);

    }

    public void notifyObservers() {

       observable.notifyObservers();

    }

    public String toString() {

       return "DecoyDuck";

    }

}

public interface Observer {

    public void update(QuackObservable duck);

}

public class Quackologist implements Observer {

    public void update(QuackObservable duck) {

       System.out.println("Quackologist:" + duck + " just quacked.");

    }

    public String toString() {

       return "Quackologist";

    }

}

总结下我们都做了什么:

1.  应用适配器模式在鸭塘中加入鹅

2.  为了计算鸭群叫声,引入装饰者模式

3.  为了避免忘记用装饰类包装,引入抽象工厂模式统一管理不同种类鸭子的创建

4.  为了方便对鸭子进行分类管理,引入复合模式应对整体-局部问题

5.  为了监控鸭子叫声,引入观察者模式

3.    另外一种组合模式—MVC

MVC被很多开发者熟悉,这里我们将从设计模式的角度来剖析这个聪明的设计,和往常一样,我们会从具体的示例入手,这次是音乐播放器。

         一般的音乐播放器都会有个界面,用于展示当前播放音乐的名称、进度、当前的播放列表等信息,当然还会提供播放、暂停、下一首等操作;音乐播放器的后台肯定有一个它所管理音乐的数据以及具体操作(播放、暂停、下一首)的实现;在两者之间,在引入一个控制器,用于翻译用户在界面上的操作,并把具体的操作转发给后台。如此,这个音乐播放器的三个部分就是经典的MVC,model-view-controller,来看看在整个过程中,三部分的职责。

1.      View是用户的操作界面,用户的意图通过在界面上的操作,由view通知controller。

2.      Controller将用户在界面上的操作翻译成对model的方法调用。

3.      某些情况下Controller也会通知view改变状态,作为用户操作的反馈。比如,点击播放按钮后,controller可能会通知view将该按钮置为不可用。

4.      当model的状态发生变化时,model主动通知view进行改变。有些改变是由用户操作引起的,比如用户点击按钮;有些改变是自动触发的,比如原有音乐播放完毕,开始播放一首新曲子。

5.      View可能会向model请求数据。比如view接到model状态变化的通知,它为了显示需要,可能会向model请求数据。(当然,model也可以将自己push出去)。

4.    不辨不明—没有傻问题

Q:之前我们看到view会作为model的观察者,是否controller也会作为model的观察者?

A:是这样的。有些情况下,比如说model状态的变化会导致view发生变化时,比如播放完成后,提示用户;这些情况下就需要controller监控到这些变化,通知view进行具体的界面变化。

Q:controller看起来只是将用户在view上的操作转发到了model,那要controller有什么用?把它的代码合并到view中不行么?

A:当然不行。从功能上讲,controller并不仅仅做转发,它需要解释用户的需求,实现将用户在view的操作跟model的方法关联。从设计的角度看,合并到一起,一方面违背了单一职责原则,使得view不仅要管理用户界面还要处理如何控制model的逻辑;另外一方面,使得view跟model紧密耦合,view将不再具有可复用性。

5.    组合模式—MVC中的设计模式

来看看MVC中应用了哪些设计模式:

1.      策略模式。view和controller应用了策略模式,view组合controller,使用controller的统一接口,只需管理和控制界面,提供一组界面管理接口,无需关心界面变化对后台model的影响。

2.      组合模式。View内部实现了组合模式,大家可能还记得我们介绍组合模式时,引用swing的frame为例。

3.      观察者模式。Model作为被观察者,view和controller作为观察者,在model状态变化时,通知到view和controller。

6.    不辨不明—没有傻问题

Q:controller是否实现业务逻辑?

A:不实现业务逻辑。它的主要工作就是把用户在界面上的操作解析成model的方法调用,有些情况下,controller需要足够聪明才能决定调用哪个model的方法,但是这个应该不属于“业务逻辑”的范畴。业务逻辑应该是管理和操作数据model中数据的哪些代码。

7.    Review

内容回顾:

a)        MVC是一种组合模式,主要包括观察者模式、策略模式和复合模式。

b)        MVC应用观察者模式从而能使得观察者获得更新的同时保持跟被观察者的松散耦合。

c)        View和controller之间应用策略模式,使得view可以通过组合不同的controller来获得不同的行为。

d)        View应用复合模式来管理前台界面的对象。

OO准则:

a. 封装变化,encapsulate what varies

b. 组合优于继承, favorcomposition over inheritance

c. 面向接口编程,program to interfaces, not implementation

d. 致力于实现交互对象之间的松散耦合, strive for loosely coupled designs between objects that interact

e. 类应该对于扩展开发,对于修改封闭, classes should be open for extension but closed for modification

f. 依赖于抽象类或者接口而不是具体类。Depend on abstraction. Do not depend on concrete classes.

g. 只跟关系最密切的对象打交道。Principle of Least Knowledge – talk only to your immediate friend.

h. 别调用我,由我在需要的时候调用你。Don’t call us, we’ll call you.

i. 单一职责原则,一个类只应该因为一个理由而变化。    Single Responsibility – A class should have only one reason tochange.

继续阅读