设计模式note[未更新完 && 排版拙劣]

本篇是head first设计模式的读书笔记

1.策略模式

策略模式定义算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户

关于继承/接口
在超类上添加的方法将会使得所有子类具备该方法,如果某一个子类不需要该方法,那么就要重写覆盖掉(空),如果多个方法不需要使用,那就要多次重写,显得很sb
如果改为利用接口实现这种难以预测的子类行为,仅在需要时才实现该接口,那代码重复的问题会更加严重,比如要修改同样100个子类的行为,那要修改100遍(现在JDK已经有default方法)

设计原则1:应用中需要变化的部分要单独封装,不可与不更改的代码耦合在一起
这样在需要更改时只需改变单独封装的部分代码即可,使得应用更容易扩展

因此我们把一个类中需要更改的部分单独分开到一组新类中代表每个行为,方便在【运行时】动态地修改行为(由此我们需要利用多态)

设计原则2:针对接口编程,不针对实现编程
也就是说原来的超类中已经把会更改的行为重新封装成新的行为类,行为类负责实现行为,不需要要原类来实现,只要确保该行为符合相应的interface即可,这样可以降低行为与固有的类的耦合程度
当然也可以使用抽象类,该原则的核心在于supertype

利用单独封装+针对接口的原则,我们可以轻易扩展行为,并且修改也不会影响到原有的类

实现上使用委托机制
1.原来的类中把变化的行为分别设为一个接口类和一个用于执行的方法,如flyBehavior接口和performFly()方法
2.要实现该行为,就在执行的方法中调用接口内部的实现,此时无需关注接口的具体对象是什么
3.如今只需关心该类所需要的具体行为是什么,依据需求可在运行时利用多态确定该行为接口的具体对象(编译时处理也可以,即把确定对象的过程放到子类的构造方法中去)

一只抽象的鸭,不知道怎样飞和怎样叫

public abstract class Duck {
    // 委托给行为类处理
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;
    
    public void performFly() {
        flyBehavior.fly();
    }
    
    public void performQuack() {
        quackBehavior.quack();
    }
    // 所有的鸭子都会有的行为
    public void swim() {
        System.out.println("All ducks float!");
    }
    // 提供运行时可以更改的做法
    public void setFlyBehavior(FlyBehavior fb) {
        flyBehavior = fb;
    }
    
    public void setQuackBehavior(QuackBehavior qb){
        quackBehavior = qb;
    }
}

其中一个接口演示

public interface FlyBehavior {
    public void fly();
}

其中一个实现接口的行为类演示

public class FlyWithWings implements FlyBehavior {

    public void fly() {
        System.out.println("Fly With Wings!")l

    }

}

一只写死的鸭子

public class MallardDuck extends Duck {
    
    public MallardDuck() {
        flyBehavior = new FlyWithWings();
        quackBehavior = new Quack();
    }
    ````
    public void display() {
        System.out.println("This is MarrlardDuck!");
    }
}

一只运行时改变行为的鸭子

public class MiniDuckSimulator {
    public static void main(String[] args) {
        Duck modelDuck = new ModelDuck();
        modelDuck.setFlyBehavior(new FlyWithRocket());
        modelDuck.performFly();
    }
}

设计原则3:多用组合,少用继承
上面的鸭子样例中的各种行为,就是通过组合来实现的

2.观察者模式

观察者模式定义了对象之间的一对多依赖(Subject和Observers),当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新

常见观察者模式的设计
1.主题接口要求实现注册/删除/通知观察者的方法
2.观察者接口要求实现自行更新的方法
3.具体主题还可实现获取状态的方法
4.具体观察者可以是实现该接口的任意类,但它们必须注册主题

设计原则4:为了交互对象之间的松耦合设计而努力
观察者模式的强大在于主题和观察者是松耦合的,它们依然可以交互,但彼此不知道具体的实现细节
这样的松耦合设计使得对象之间的依赖降到最低

下面来设计一个天气通知和多个订阅者

用于主题的接口

package com.design.observer;

public interface Subject {
    public void registerObserver(Observer o);
    public void removeObserver(Observer o);
    public void notifyObservers();
}

用于观察者的接口

package com.design.observer;

public interface Observer {
    public void update(float temp, float humidity, float pressure);
}

用于天气显示公告板的接口

package com.design.observer;

public interface DisplayElement {
    public void display();
}

主题的实现类

package com.design.observer.impl;

import java.util.ArrayList;

import com.design.observer.Observer;
import com.design.observer.Subject;

public class WeatherData implements Subject {

    private ArrayList observers;
    private float temperature;
    private float humidity;
    private float pressure;
    
    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 = (Observer)observers.get(i);
            observer.update(temperature, humidity, pressure);
        }
    }
    
    public void measurementsChanged() {
        notifyObservers();
    }
    
    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}

观察者的实现类

package com.design.observer.impl;

import java.util.ArrayList;

import com.design.observer.Observer;
import com.design.observer.Subject;

public class WeatherData implements Subject {

    private ArrayList observers;
    private float temperature;
    private float humidity;
    private float pressure;
    
    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 = (Observer)observers.get(i);
            observer.update(temperature, humidity, pressure);
        }
    }
    
    public void measurementsChanged() {
        notifyObservers();
    }
    
    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}

这种写法有点粗糙,比如当订阅者订阅了多个主题时,到了需要更新的时候就不知道要update哪些,不过问题不大,稍微改改就能用了(利用instanceof),但Java已经为我们提供了一个Observable类(注意),可以直接使用一个设计良好的板子,但由于继承是单亲的,这样做是一种浪费,更何况还违背了多用组合少用继承的设计原则,因此多建议手写(又不难是吧)

3.装饰者模式
(给爱用继承的你)

装饰者模式动态地把责任附加在对象上,若要扩展功能,装饰者比继承更有弹性的替代方案

装饰者对象的类型反映了所装饰的对象

设计原则5:类应对扩展开放,对修改关闭(简称开闭原则)
修改现有的代码可能会造成隐患,所以在接受新的功能时不应修改原有代码

暂略

4.工厂模式

工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

工厂处理创建对象的细节,它能更好地遵循前面提到的对修改关闭/针对接口编程/封装变化的部分等OO原则

试想一个类型在new定义具体对象,但对象种类繁多,如果在创建时用多层if-else来判断具体类型,到后期的增删改会是非常麻烦的一件事,并且如果在多处出现这种情况,后果更是严重,因此,把创建的部分封装成一个工厂来专门处理这些细节是有必要的

工厂方法是抽象的,依赖子类来处理对象的创建,这样超类的代码与子类的创建就能实现解耦(你需要子类创建的对象具有一定的规范,这时候在抽象的超类中有具体的执行方法和抽象的工厂方法,这就实现了子类的区分与相应的约束,也就是说各个子类决定创建的对象有所不同,但执行方法都在超类的掌控之下)(语死早,见谅)

设计原则6:要依赖抽象,不要依赖具体类(依赖倒置原则)
对于一个多个具体类的类,我们可以从中构造一个抽象类来管理多个具体类,然后让该类依赖于单一的抽象类(工厂方法等)

8.模板方法模式

模板方法在一个方法中定义一个算法的股价,而将一些步骤延迟到子类中。

模板方法使子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤

基本的设计方法:对于两个或多个步骤相似的算法,我们可以为它们抽象出一个基类,把算法的主要步骤放在里面,并且将步骤一致的行为也封装在里面(不希望在子类中改变,因此都设为final,不同之处再由子类分别实现(在基类中提供抽象方法)

对模板方法进行挂钩:钩子hook作为方法声明在抽象类中,但只有空的或默认的实现。钩子的存在使得子类可对算法的不同点进行挂钩

设计原则:Don't call us, we'll call you(好莱坞原则)

前面提到,钩子在基类中提供空或默认的实现,针对算法的不同点,我们要在子类重写该钩子方法,并且被基类所调用,这样就实现了挂钩(初次见识到这种写法是在Servlet的初始化过程,感觉特别机智)。

在这里总结一下:

策略模式是 1.封装可互换的行为 2.使用委托来选择行为

工厂方法模式是 由子类决定实例化具体类

模板方法模式是 由子类决定实现算法中的步骤

9.迭代器与组合模式

迭代器模式提供一种方法顺序访问一个聚合对象的各个元素,而又不暴露其内部的表示

组合模式允许你将对象组合成树形结构来表示整体/部分,组合能让客户以一致的方式处理个别对象以及对象组合

迭代器模式依赖于迭代器接口,多个数据结构使用统一的接口来实现多态地进行操作,而不需要知道具体的数据结构,使用者不必关心内部实现的细节,从而达到解耦的目的,比如要实现一个遍历不知何种数据结构的方法,只需传入Iterator对象并调用各自实现好的next()即可,调用者不会知道内部是什么

在Java中已经有Iterator接口,需要支持遍历和删除(后者不需要时可抛出一个运行时异常)

设计原则:一个类应该只有一个引起变化的原因

书上写的有点那啥,个人认为是类保持高内聚以降低出错的可能和排查错误的时间成本

当多个数据结构合并到一起用一个超类列表来统一处理时,我们便能用迭代器方便的对各种数据结构逐一地遍历或其他操作,但注意到这些数据结构之间的关系已经是线性的,如果要求某个数据结构中再多一层展开,我们必须依照树形关系来维护(比如map的map,list的list),树形+遍历,不难想到递归,但在此之间要先设好规定

在树形的关系中,某个对象到底是代表整体的数据结构Composite,还是仅代表数据结构中的某个元素Leaf,这是需要区分的,但我们为它们都设同一个超类Component,这样便能在添加/修改过程中对Component做一个统一个处理(一个巧妙的设计是Component.add(Component)),消除了部分和整体的区分,当然个体并不支持添加/删除子结构的操作,要抛出不支持的异常。设计好后依然可以用外在为线性的结构来封装各种数据结构,不必真的使用显式的树形结构(虽然确实是树形),详见代码(暂留orz)

简而言之,组合模式可以让对象的集合和个别的对象一视同仁

PS.Uva上的集合栈计算器的做法也有用到这种思想

附:空迭代器在设计上并不意味着null,只是hasNext()永远返回false

10.状态模式

猜你喜欢

转载自www.cnblogs.com/caturra/p/10548200.html