设计模式——葵花宝典

(源自:http://tianliezhihen.blog.hexun.com/5363294_d.html)
Head First Design Patterns 学习笔记

学习设计模式,首先要让自己明白,为什么要“设计模式”?她的好处在哪里?
《Head First Design Patterns》的第一章即阐述这个问题。

Remember, knowing concepts like abstraction,inheritance,polymorphism do not make you a good object oriented designer.
A design guru thinks about how to create flexible designs that are maintainable and that can cope with change.
                ——摘自《Head First Design Patterns》
记住,懂得抽象、继承和多态的概念不能使你成为一个好的面向对象的设计者。设计高手总是思考如何构建便于维护的和善于应变的灵活设计。

我的理解:设计模式是一种思想,又是一种技术,可以说是理论与实践相结合的部分,而项目就是由理论和实践相结合的结果。对面向对象的思想有着深刻的理解,并不一定能写出漂亮的代码;同样,掌握了各种ArrayList等集合的各种方法,不一定就能写出高性能易维护的集合应用。思想于技术结合的实践有一个重要的目标就是造就出flexible design。

Someone has already solved your problems!
                ——摘自《Head First Design Patterns》
有人已经解决了你的问题!

我的理解:设计模式是实践经验的总结,是真理。通过前辈们的摸索,慢慢总结出了一套设计flexible design的普遍规则,这种规则是经由长期的实践检验,并被证明是正确的。所以我们应该站在巨人的肩膀上向前走,不能视前人的真理性认识于不顾,继续制造毫无模式的垃圾代码(难于维护,不可重用,不可变化)。

葵花宝典》第一式——分离并包装变化的部分(Seperating & Encapsulating)
 

Design Principle : Identify the aspects of your application that vary and seperate them form what  stays the same.
                ——摘自《Head First Design Patterns》
设计宝典:正确辨认出你的应用中发生变化的部分,并把他们从那些稳定不变的部分中分离出来。

Take what varies and "encapsulate" it so it won't affect the rest of your code.
The result? Fewer unintended consequences from code changes and more flexibility in your system.
                ——摘自《Head First Design Patterns》
设计宝典:
    将不确定(变化)的部分拿出来,封装起来。
    结果是,减少因代码变更而引起的不可预知的结果,并且让你的系统更富灵活性。


我的理解:
书中用了一个SimUDuck 的模拟鸭子游戏系统为例。一开始,系统设计人员(Joe)利用OOD,将所有的鸭子抽象为一个基类Duck,具有共同的swim()和display()方法,由这个积累派生出许多派生类,如野鸭MarllaDuck、橡皮鸭RubberDuck等。随着市场竞争的加剧,Joe的上司决定,为了在竞争中获取优势,必须创造出某些不一样的东西,这种不一样的东西就是,让鸭子可以飞起来。Joe接到新的要求,觉得很简单,只需要在Duck基类中加入一个fly()方法,这样鸭子们就具有了飞的能力了。 (很显然,面向对象OO的思想和设计确实能带来巨大的方便)。Joe这样修改好系统后,Joe的上司跟董事会掩饰系统,结果出现了出人意料的结果:一个橡皮鸭在屏幕上飞来飞去!很明显这是违背常理的,因此这样的设计是有缺陷的。 (同样很显然,如果没有好的设计,OO带来的或许不是方便)。这里我们可以发现,OO继承机制并非适合所有的问题。

 

这时的Joe发现了自己系统的“slight flaw”(小小的缺陷)。于是不得不改进。并非所有的鸭子都能飞,将fly()方法放在基类,导致了所有的子类鸭子都可以飞,无论是木鸭子、橡皮鸭还是野鸭。Joe这时的方法是将飞的动作(还加入了其他动作)抽象为一个接口:Flyable,会飞的鸭子实现这个接口,其他不能飞的鸭子不实现这个接口。此时系统还加入了一个功能,鸭子能够呱呱叫:Quack,同样Joe抽象出Quackable接口,让那些会叫的鸭子都实现这个接口。(接口是OO抽象的精华,这时的系统会怎样呢?)。Joe的上司是这样说的:“That is , like , the dumbest idea you've come up with”(最愚蠢的Idea)。为什么呢?试想加入系统加入了很多会飞的鸭子,势必得让这些鸭子都实现Flyable接口,会叫的还要实现Quackable接口,也许还要加入其他功能,就不得不实现其他接口,因此就不得不在这些新的鸭子类中,不断拷贝相同的Flyable和Quackable实现代码。并且,如果要更改fly()的实现,势必必须更改如此多的子类代码(48种)。因此,这种设计也不是一个Flexible Design。

如果你是Joe,你该怎么办?

对于这个模拟鸭子游戏而言,fly(), quack()都是变化的部分,这些功能是根据用户或市场的需要加入的,或许将来还会由更多的功能,同样也有可能减去某些功能。我们必须把这些不断变化的部分同那些不变的部分分离开来,使得我们变化的部分逻辑更改不会影响其他功能,不会影响系统的正常运行,而且可以将精力几种到如何实现fly()等变化的功能(这不会影响到其他部分)。

那么,应该怎样分离这些变化的功能呢?可以将fly行为和quack行为抽象为单独的类,具体的实现见下一篇。

《葵花宝典》第二式——面向接口编程,而非面向实现编程(Program to an interface,not an implementation)

Program to an interface,not an implementation
                ——摘自《Head First Design Patterns》
面向接口编程,而非面向实现编程。

还是那只鸭子!
可怜的Joe在经过继承和接口的梦魇之后,不得不考虑重新设计系统——必须将变化的部分从不变的部分中分离出来。该怎样实现呢?

第一:分离变化的功能(分离fly功能)
首先,我们把fly功能抽象出来,专门负责fly事宜。假如命名为FlyBehavior(实现见表格一),那么我们的Duck类就可以通过FlyBehavior来控制fly的功能。(实现见表格二)
表格一(FlyBehavior 类):

package seperating;

public abstract class FlyBehavior {
    protected int speed;//飞行速度
    private int maxSpeed;
    private int minSpeed;
    public void speedUp(int accelaration){//加速
        if(speed == maxSpeed || speed == minSpeed)
            return ;
        speed += accelaration;
        if(speed < minSpeed)
            speed = minSpeed;
        if(speed > maxSpeed){
            speed = maxSpeed;
        }
    }


    public abstract void fly();

    public int getMaxSpeed() {
        return maxSpeed;
    }

    public int getMinSpeed() {
        return minSpeed;
    }

    protected void setMaxSpeed(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }

    protected void setMinSpeed(int minSpeed) {
        this.minSpeed = minSpeed;
    }
}


表格二(Duck 类):

package seperating;

public abstract class Duck {
    public void swim(){//所有的鸭子都会游泳,这是不变的部分
        System.out.println(this + "说:^_^,我会游泳哦");
    }
    public String toString(){
        return "鸭子祖宗";
    }

    //分离出来的变化的部分,飞的形式,速度等等都不一样
    //public abstract FlyBehavior getFlyBehavior();
    private FlyBehavior flyBehavior;
    //提供setter,供运行时更改飞行行为
    public void setFlyBehavior(FlyBehavior flyBehavior){
        this.flyBehavior = flyBehavior;
    }

    public void fly(){
        flyBehavior.fly();
    }
}



第二:面向接口的编程
在Duck类中,分离FlyBehavior飞行行为时,我们操作的是FlyBehavior,我们不知道到底是不是可以飞,飞的速度到底是多少这些具体的细节实现,我们也无须知道,对于Duck而言,只要发出指令飞Fly就可以了,到底怎么飞的,与Duck无关。所以对于Duck而言只需要这样一个功能性的接口而无须知道实现,这就是面向接口编程。(见表格三)
同时Duck类本身是一个抽象类,因此在使用Duck时,也是面向接口,我们不能new 一个Duck实例,只能new她的子类(实现类,concrete类),但是在操作Duck时,我们不知道她到底是RubberDuck还是MallardDuck,只需要调用Duck公共的接口:swim()和fly()。(见表格四)

表格三(FlyBehavior 的具体实现类 Concrete Class)

package seperating;
//不能飞行

public class FlyNoWay extends FlyBehavior {
    public void fly() {
        System.out.println("不能飞行/n速度为:"+speed);
    }
}

package seperating;
//慢速飞行

 

public class FlySlowly extends FlyBehavior {
    public FlySlowly() {
        setMaxSpeed(100);
        speed = 50;
    }

    public void fly() {
        System.out.println("我能慢速飞/n速度:"+speed);
    }
}

 

package seperating;
//快速飞行

 

public class FlyFast extends FlyBehavior {
    public FlyFast() {
        setMaxSpeed(1000);
        speed = 500;
    }

    public void fly() {
        System.out.println("我能快速飞/n速度:"+speed);
    }
}



表格四(Duck 的 Concrete Class):

package seperating;
//橡皮玩具鸭,不能飞

public class RubberDuck extends Duck {
    public RubberDuck() {
        setFlyBehavior(new FlyNoWay());
    }
    public String toString(){
        return "橡皮玩具鸭";
    }

    //public FlyBehavior getFlyBehavior() {
    //    return new FlyNoWay();
    //}

 

}

package seperating;
//野鸭,快速飞

 

public class MallardDuck extends Duck {
    public MallardDuck() {
        setFlyBehavior(new FlyFast());
    }
    public String toString(){
        return "野鸭";
    }

    //public FlyBehavior getFlyBehavior() {
    //    return new FlyFast();
    //}
}

 

 

package seperating;
//家鸭,慢速飞

public class FowlDuck extends Duck {
    public FowlDuck() {
        setFlyBehavior(new FlySlowly());
    }
    public String toString(){
        return "家鸭";
    }
    //public FlyBehavior getFlyBehavior() {
    //    return new FlySlowly();
    //}
}



第三:工厂类的使用
由于系统里存在很多种鸭子,如果每个都用new,然后进行初始化,将是一个非常dumb的设计。可以引入工厂模式。(见表格五)
表格五(DuckFactory 类,负责生产鸭子):

package seperating;
//鸭子工厂,容易增加新类型的鸭子

public class DuckFactory {
    public static final int RUBBER_DUCK = 1;
    public static final int MALLARD_DUCK = 2;
    public static final int FOWL_DUCK = 3;

    public static Duck createDuck(int DuckType) {
        switch (DuckType) {
        case  RUBBER_DUCK:
            return new RubberDuck();
        case  MALLARD_DUCK:
            return new MallardDuck();
        case  FOWL_DUCK:
            return new FowlDuck();
        }
        return null;
    }
}



表格六(程序测试类,也掩饰了Duck 的面向接口编程):

package seperating;

public class DuckGame {
    public static void main(String[] args) {
        Duck[] duck = new Duck[] {DuckFactory.createDuck(DuckFactory.FOWL_DUCK),
                      DuckFactory.createDuck(DuckFactory.MALLARD_DUCK),
                      DuckFactory.createDuck(DuckFactory.RUBBER_DUCK)};
        for (int i = 0; i < duck.length; i++) {

            duck[i].swim();
            duck[i].fly();
        }//操作的都是Duck
    }
}
/**输出结果
家鸭说:^_^,我会游泳哦
我能慢速飞
速度:50
野鸭说:^_^,我会游泳哦
我能快速飞
速度:500
橡皮玩具鸭说:^_^,我会游泳哦
不能飞行
速度为:0

*/



要注意的问题:面向接口的编程中的接口(interface)与java中的interface关键字内涵不同,前者包括了后者,同时还包括抽象基类。
可以扩展的地方:可以尝试动态修改鸭子的fly,可以修改一下Duck的getFlyBehavior,给这个方法增加一个参数,接受新的FlyBehavior,也可以用其他set方法实现。

《葵花宝典》第三式——组合优于继承(HAS-A can be better than IS-A)
Design principle: Favor composition over inheritance.
葵花宝典:喜爱组合比喜爱继承多一点。

我的理解:
继承和组合都可以实现代码的重用(Reuse),为什么组合要优于继承呢?
回想SimUDuck这个例子,颇有感触,开始Joe的方案是用继承,代码改动简单,可以实现现有情况下的某些需求(大部分鸭子都是正常的,但有一小部分是失败的,比如RubberDuck);仔细思考继承:通过继承,子类能够获得与父类相同的功能和属性,父类拥有了某种功能,子类就有了;但是如果子类需要新的功能,直接在父类里面增加新的功能,那么会导致所有的子类同时拥有这种功能,这实际上是将类的功能与类紧紧的耦合在一起,是一种紧密耦合系统,由于关系相当密切,导致改动一处而动全身,实际上不利于代码的重用。而组合恰好相反,她把类的各项功能分割开,互不干涉。一个类的功能,实际上是各种子功能的组合,比如鸭子的功能就是飞行功能和发出叫声功能的组合,无论是何种鸭子,都是这样。同时到底是怎样飞行,怎样叫,又可以通过面向接口的编程实现多态。因此这个组合的系统(Duck)很容易在加入新的成员,比如加入ModelDuck,加入RocketDuck,只需要实现新的相应的飞行和叫的功能类(实现功能接口)。很明显,组合所产生的代码便于维护,易于扩展,是真正的Flexible Design。


《葵花宝典》第四式——Strategy Pattern 

第四式是对前三式的总结。其实前三式就是策略模式(Strategy Pattern)的应用。

The Strategy Pattern : It defines a family of algorisithms , encapsulates each one and, makes them interchangable.
Strategy lets the algorithm vary indepently from clients that use it.

策略模式:定义了一族算法,每一个算法都被包装起来,并且是可以互换的。
策略模式使得算法的改变与算法的使用者(客户程序)相互分离,算法的改变不会影响到客户程序。

我的理解:
1、算法的包装用到面向对象的封装技术。
2、算法互换用到面向对象中面向接口编程。
3、算法的改变与算法使用者的分离运用到面向对象中代码重用之组合的思想。

所以,关键点,我觉得是第一式,分离变化的部分并包装她。核心思想应该是Composition(组合)重用。实施细节上当然是OO技术的使用,综合所有方可造就 Flexible Design(良好的OOD)。


《葵花宝典》第五式——Observer Pattern 

Design Principle: Strike for loosely coupled designs between objects that interact.
Loosely coupled designs allow us design flexible OO systems that can hadle change because they minimize the interdependency betweenn objects.
葵花宝典:在设计相互作用的对象时,尽量采用松散耦合的设计。
松散耦合的设计允许我们构建能够掌控变化的灵巧的OO系统,因为松散耦合的设计将对象间的依赖关系降低到最小程度。

我的理解:松散耦合设计是设计模式的重要思想之一,许多模式都力图达到这一目标。比如前面学习的Stratigy Pattern,就是将变化的部分从类中剥离出来,形成单独的类,使变化的部分与不变的部分原来仅仅的集成在一起的状态变成较为松散的关系,象鸭子的飞(fly)的行为由专门的飞行类负责,结果是变得易扩展,易维护。所以在今后的设计中力图达到这个目标。


Observer Pattern : The observer pattern defines a one-to-many dependency between objects so that when one object changes state,all of its denpentts are notified and updated automatically.
葵花宝典:观察者模式定义了一个“一对多”的对象间关系,当某个对象状态变更时,所有依赖他的对象都会得到通知,并且能够自动更新自身。

我的理解:什么时候会用到观察者模式呢?
    我想到一个例子:当人们或者车们过马路时,会看一下红绿灯,当红灯亮的时候,会听下来,当红灯灭了,便开始过马路。可以把红灯与行人以及汽车的关系理解为“一对多”的关系,行人和汽车都依赖于红绿灯。于是可以把红灯定义为一个可观察的对象,行人和汽车都是观察者。
    还有一个假想的例子:有一个奇怪的老师,上课时如果学生惹他生气了,他会让所有的人狂笑10分钟,因此最后,没有一个学生敢再课堂上惹他生气。这时一个老师和N多学生也构成“一对多”的关系。

这两个例子有一些区别,第一个例子中,红灯亮的时候,不一定所有的车子都会停下,当然有一些人也不会停下(他们都嫌自己获得太长寿了),所以观察者站主动地位,可以根据我的需要对红灯的状态变化做不同的反映。但是第二个例子就不一样了,老师要你狂笑的时候,你必须得笑,要不然就开除你!因此老师站主动地位。
这两种观察者模式在java里已经有实现了(Observable 类 和 Observer接口)。

 

下面的例子是我自己的实现:

//所有的可观察的对象皆实现Observable接口
package observer;

public interface Observable {
    void addObserver(Observer observer);
    void deleteObserver(Observer observer);
    void notifyChanged();
}


//一个通用的可观察者类,实现了Observable接口
package observer;

import java.util.ArrayList;

public abstract class GenericObservable implements Observable {
    private boolean changed;//if changed is true,notify all observers really
    private ArrayList observers;//save all observers
    private Object updateArg;//notify arg
    public GenericObservable() {
        observers = new ArrayList();
    }
    
    public void setChanged(){
        changed = true;
    }
    /**
     * addObserver
     *
     * @param observer Observer
     * @todo Implement this observer.Observable method
     */
    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    /**
     * deleteObserver
     *
     * @param observer Observer
     * @todo Implement this observer.Observable method
     */
    public void deleteObserver(Observer observer) {
        observers.remove(observer);
    }

    /**
     * notifyChanged,if changed is true,notify all observers
     *
     * @todo Implement this observer.Observable method
     */
    public void notifyChanged() {
        if(this.changed){
            for(int i = 0; i < observers.size(); i ++){
                ((Observer)observers.get(i)).update(this,this.updateArg);
            }
        }
        changed = false;
    }

    public Object getUpdateArg() {
        return updateArg;
    }

    public void setUpdateArg(Object updateArg) {
        this.updateArg = updateArg;
    }
}



//观察者接口
package observer;

public interface Observer {
    void update(Observable o , Object arg);
}



package observer;
/**
 * 
 * <p>Title: </p> 
 * 
 * <p>Description: 是一种被动的可观察对象,
 * 红灯熄灭和闪亮两种状态的改变不会自动通知马路上的人或车,
 * 人或车等其他对象应该主动观察,如果状态发生改变,就可以过马路了,
 * </p> 
 * 
 * <p>Copyright: Copyright (c) 2006</p> 
 * 
 * <p>Company: </p>
 * 
 * @author not attributable
 * @version 1.0
 */
public class RedLight extends GenericObservable {
    
    public RedLight() {
        super.setUpdateArg(true);
    }
    public void on(){
        setChanged();
        setUpdateArg(true);
        super.notifyChanged();
    }
    public void off(){
        setChanged();
        setUpdateArg(false);
        super.notifyChanged();
    }
}


//安全的小汽车,是红绿灯的观察者
package observer;

public class Car implements Observer {
    RedLight redLight;//观察红灯
    private String registrationMark;//汽车牌照
    public Car(RedLight redLight , String registrationMark) {
        observeRedLight(redLight);
        this.registrationMark = registrationMark;
    }
    private void observeRedLight(RedLight redLight){
        this.redLight = redLight;
        redLight.addObserver(this);
    }
    /**
     * update
     *
     * @param o Observable
     * @param arg Object
     * @todo Implement this observer.Observer method
     */
    public void update(Observable o, Object arg) {
        boolean b =(Boolean) arg;
        System.out.println(this.registrationMark + (b ? " Say:RedLight is on. I must -| STOP <-" : "car will |-RUN->"));
    }
}



//可怕的老师,也是可观察者
package observer;
/**
 * 
 * <p>Title: </p> 
 * 
 * <p>Description: Bad Teacher is very bad.When he is angry, 
 * he will let all of his students laugh their head off
</p> 
 * 
 * <p>Copyright: Copyright (c) 2006</p> 
 * 
 * <p>Company: </p>
 * 
 * @author not attributable
 * @version 1.0
 */
public class BadTeacher extends GenericObservable {
    
    public BadTeacher() {
    }
    
    public void turnAngry(){
        super.setChanged();
        super.notifyChanged();
    }
    
}



//可怜的学生,被迫的
package observer;
/**
 * 
 * <p>Title: </p> 
 * 
 * <p>Description: Pitiful Student  is a student of BadTeacher</p> 
 * 
 * <p>Copyright: Copyright (c) 2006</p> 
 * 
 * <p>Company: </p>
 * 
 * @author not attributable
 * @version 1.0
 */
public class PitifulStudent implements Observer{
    private BadTeacher teacher;
    public PitifulStudent(BadTeacher teacher) {
        this.teacher  = teacher;
        this.teacher.addObserver(this);
    }

    public void update(Observable o, Object arg) {
        System.out.println("这个变态的老师,又让我们狂笑!哈哈哈哈哈哈哈哈哈哈哈哈");
    }
}



最后是主程序:

package observer;

public class ObserverTest {
    public static void main(String[] args) throws InterruptedException {
        //红灯,被动的可观察者
        RedLight redLight = new RedLight();
        
        //两个安全的小汽车,观察红灯是其天生的功能
        Car car1 = new Car(redLight , "南 AAAAA");
        Car car2 = new Car(redLight,"沪C 00000");

        
        //红灯亮了,所有汽车停下来了
        redLight.on();
        
        //亮3秒
        System.out.println("红灯亮3秒");
        Thread.sleep(3 * 1000);
        
        //红灯灭,所有汽车跑了
        redLight.off();
        
        
        //坏老师,主动的可观察者,一生气,学生必须狂笑
        BadTeacher teacher = new BadTeacher();
        PitifulStudent student = new PitifulStudent(teacher);//可怜的学生
        teacher.turnAngry();//生起了
       
    }
}

你可能感兴趣的:(设计模式——葵花宝典)