程序员社区

Head First设计模式读书笔记


title: Head First设计模式
date: 2019/06/24 13:39


我们总是需要花费大量的时间在系统的维护和变化上,所以应该注重于代码的可维护性和高扩展性。

一、策略模式

某公司做了一个鸭子的游戏,里面有各种各样的鸭子,于是开发者定义了下面这样的一个超类

public abstract class Duck {

    /**
     * 叫
     */
    public void quack() {
        System.out.println("呱呱叫!");
    }

    /**
     * 游泳
     */
    public void swim() {
        System.out.println("游泳中!");
    }

    /**
     * 外貌
     */
    public abstract void display();

    // ===========================

    /**
     * 增加一个飞行行为
     */
    public abstract void fly();
    
}

所有的子类鸭子继承这个类,实现display的方法体;此时,公司需要增加一个飞行行为,于是小明在上面的接口中增加了一个fly()抽象方法。

然后就会造成一个问题,就是所有的子类都要重新实现这个这个抽象方法(这个没法解决),而且重复的代码没有办法复用。

思考:那怎么办呢?

  1. 写一个类让子类继承(Java不支持多继承)
  2. 将变化的行为找出,为该行为书写接口和实现类,并使用复合代替继承(√)

实现:

设计原则1:找出易变的地方,将其独立出来,不要和不会变的代码混合在一起。

quack() 嘎嘎叫、呱呱叫、不会叫

swim() 都会游泳

display() 每种鸭子不同(不可复用,不会改变

fly() 会飞、不会飞

所以我们要将quack()和fly()方法抽取出来

设计原则2:变量的声明类型应该是超类型(接口、抽象类)

设计原则3:多用组合少用继承,如果不是特别确定是它的子类就不要用继承

继承关系:is-a 是一个

实现关系:has-a 有一个

/**
 * 飞行行为和他的子类们
 */
public interface FlyBehavior {
    void fly();
}

public class FlyWithWings implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("使用羽毛飞!");
    }
}

public class FlyNoWay implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("不会飞!");
    }
}

/**
 * 鸭叫行为和它的子类们
 */
public interface QuackBehavior {
    void quack();
}

public class Quack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("呱呱叫!");
    }
}

/**
 * 崭新的鸭子类
 */
public abstract class Duck {

    // 维持两个行为的变量
    private FlyBehavior flyBehavior;

    private QuackBehavior quackBehavior;

    /**
     * 叫
     */
    public void quack() {
        quackBehavior.quack();
    }

    /**
     * 游泳
     */
    public void swim() {
        System.out.println("游泳中!");
    }

    /**
     * 外貌
     */
    public abstract void display();

    /**
     * 飞行
     */
    public void fly(){
        flyBehavior.fly();
    }

    public Duck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) {
        this.flyBehavior = flyBehavior;
        this.quackBehavior = quackBehavior;
    }

    public void setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }

    public void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }
}

/**
 * 野鸭(子类/客户端)
 */
public class MallardDuck extends Duck {

    public MallardDuck() {
        super(new FlyWithWings(), new Quack());
    }

    public MallardDuck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) {
        super(flyBehavior, quackBehavior);
    }

    /**
     * 外貌
     */
    @Override
    public void display() {
        System.out.println("黄色的!");
    }
}

/**
 * 客户端调用
 */
public static void main(String[] args) {
    MallardDuck mallardDuck = new MallardDuck();

    mallardDuck.fly();
    mallardDuck.quack();

    // 动态改变飞行行为
    mallardDuck.setFlyBehavior(new FlyNoWay());
    mallardDuck.fly();
}

定义:

定义一系列的算法,把它们一个个封装起来,让它们可相互替换

封装:可以复用

相互替换:解决算法需要动态替换的场景(例如:商场有一个开关,打开的话全场的商品7折,使用策略模式就可以动态切换算法)

Head First设计模式读书笔记插图

使用场景:

  1. 某种行为/算法可以被很多地方用到
  2. 算法需要动态替换
  3. 多重判断导致的代码量庞大
  4. 对外隐藏代码实现细节

其它参考文章

https://www.runoob.com/design-pattern/strategy-pattern.html

https://www.zhihu.com/question/31162942

二、观察者模式/发布订阅模式

高内聚 低耦合

高内聚:模块中的方法在许多方面都很类似即做相似事情的功能封装在同一个模块中。

低耦合:模块之间的依赖程度要尽可能小;通过一个简单又稳定的接口进行通信,不关心他是怎么实现的。

例如,查询审查任务,这个功能是ars系统的功能,但是数据在ams系统中,如果我们直接去他们的数据库中去取,那么我们就和ams系统耦合在一起了,如果以后不用ams系统了,我们的系统所有和审查任务有关的逻辑全部要修改。

一般而言高内聚性代表低耦合性,反之亦然。

如果所有模块的内聚性强,那么其它模块引用的模块就减少了。

已知有一个类WeatherData用来检测气象站的信息,当气象测量更新,我们要将布告板上的数值修改。

public class WeatherData {

    // 温度
    public String getTemperature() {
        return null;
    }

    // 湿度
    public String getHumidity() {
        return null;
    }

    // 气压
    public String getPressure() {
        return null;
    }

    /**
     * 如果气象测量更新会调用这个方法
     */
    public void measurementsChanged() {
        
    }
}

错误示范:

public void measurementsChanged() {
    String temperature = getTemperature();
    String humidity = getHumidity();
    String pressure = getPressure();

    // 问题:直接对具体对象进行编程,如果增加布告板则需要修改这部分代码
    currentConditionsDisplay.update(temperature, humidity, pressure);
    statisticsDisplay.update(temperature, humidity, pressure);
    forecastDisplay.update(temperature, humidity, pressure);
}

设计原则4:对象之间应该松耦合

定义:

观察者模式定义了对象之间的一对多关系,当对象改变状态,其它依赖者都会收到通知。

Head First设计模式读书笔记插图1

观察者模式使主题和观察者之间解耦(不知道彼此的实现细节)

实现:

1)自己实现

public interface Subject {

    /**
     * 注册观察者
     *
     * @param observer
     */
    void registerObserver(Observer observer);

    /**
     * 移除观察者
     *
     * @param observer
     */
    void removeObserver(Observer observer);

    /**
     * 通知观察者们
     */
    void notifyObservers();
}

public class WeatherData implements Subject {

    private float temperature;
    private float humidity;
    private float pressure;

    // 温度
    public float getTemperature() {
        return temperature;
    }

    // 湿度
    public float getHumidity() {
        return humidity;
    }

    // 气压
    public float getPressure() {
        return pressure;
    }

    /**
     * 如果气象测量更新会调用这个方法
     */
    public void measurementsChanged() {
        this.notifyObservers();
    }

    private List<Observer> observers = new ArrayList<>();

    /**
     * 注册观察者
     *
     * @param observer
     */
    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    /**
     * 移除观察者
     *
     * @param observer
     */
    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    /**
     * 通知观察者们
     */
    @Override
    public void notifyObservers() {
        observers.forEach(observer -> observer.update(temperature,humidity,pressure));
    }
}

public interface Observer {

    void update(float temperature, float humidity, float pressure);
}

/**
 * 所有的布告板都要实现这个接口
 */
public interface DisplayElement {

    void display();
}

public class CurrentConditionsDisplay implements Observer,DisplayElement {

    private Subject subject;

    private float temperature;
    private float humidity;

    public CurrentConditionsDisplay(Subject subject) {
        this.subject = subject;

        subject.registerObserver(this);
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
        // 获取该布告板需要的数据
        this.temperature = temperature;
        this.humidity = humidity;

        this.display();
    }

    @Override
    public void display() {
        System.out.println("xxx");
    }

    public static void main(String[] args) {

        Subject subject = new WeatherData();

        Observer observer = new CurrentConditionsDisplay(subject);
        // 等待气象信息改变就行了
    }
}

P59,学完MVC模式要看一下

2)使用JDK提供的

我们上面实现的方式是主题主动将信息推给我们的,这样就会导致,有些参数我们不需要,但是它还是给我们了,JDK提供的观察者为我们提供了“推”、“拉”两种方式。

public class WeatherData extends Observable {

    private float temperature;
    private float humidity;
    private float pressure;

    // 温度
    public float getTemperature() {
        return temperature;
    }

    // 湿度
    public float getHumidity() {
        return humidity;
    }

    // 气压
    public float getPressure() {
        return pressure;
    }

    /**
     * 如果气象测量更新会调用这个方法
     */
    public void measurementsChanged() {
        // 调用这个方法,表示数据改变
        super.setChanged();

        // notifyObservers有两个重载,一个是传参的(推)、一个是不传参的(拉)
        // 我们在这就使用拉的方式,所以需要提供上面的3个get方法。
        super.notifyObservers();
        // super.notifyObservers("xxx");
    }
}

public class CurrentConditionsDisplay implements Observer, DisplayElement {

    private Observable observable;

    private float temperature;
    private float humidity;

    public CurrentConditionsDisplay(Observable observable) {
        this.observable = observable;

        observable.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {
        if (o instanceof WeatherData) {
            WeatherData weatherData = (WeatherData) o;

            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();

            this.display();
        }
    }

    @Override
    public void display() {
        System.out.println("xxx");
    }

    public static void main(String[] args) {

        Observable weatherData = new WeatherData();
        Observer observer = new CurrentConditionsDisplay(weatherData);
        // 等待气象信息改变就行了
    }

}

上面演示了拉的方式,但在“推”的方式被认为更正确。

JDK提供的观察者缺点:

  1. Observable是一个类,而且它还没有实现接口,所以限制了继承
  2. 它将setChange()设置为protected的了,导致我们没有办法使用组合来解决多继承。

所以最好实际使用的时候自己实现一套,注意要考虑并发情况下偶。

使用场景

https://www.runoob.com/design-pattern/observer-pattern.html

三、装饰者模式

/**
 * 饮料超类
 */
public abstract class Beverage {

    private String description;

    public String getDescription() {
        return description;
    }

    public Beverage(String description) {
        this.description = description;
    }

    /**
     * 获取商品总价的方法
     */
    public abstract double cost();

}

星巴克中有很多的饮料,而且他们有的需要加一些配料,如果我们将饮料和一大堆配料进行排列组合的创建类的话就会造成类爆炸。

Head First设计模式读书笔记插图2

我们想一想,有没有别的解决办法?

我们可以修改一下Beverage类,将每种配料在类中声明一个boolean值,cost()方法负责将所有配料的价格计算出来,子类只需要调用super.cost() + 该饮料的价格

上面的方式虽然看着没有问题,但是如果要增加一个配料的话就需要修改Beverage类的代码,这违反了开闭原则

设计原则5:对扩展是开放的,对修改关闭。

定义:

动态的将职责附加到对象上。如果要扩展功能,装饰者提供了比继承更有弹性的替代方案。

因为继承是在编译时期就确定好的中,而组合是在运行时动态的确定的。

例如:如果我需要一个加摩卡和奶泡的咖啡,一个加奶泡的牛奶

使用继承:

饮料 <- 咖啡 <- 摩卡奶泡咖啡
<- 牛奶 <- 奶泡牛奶

使用组合:

饮料 <- 咖啡
<- 牛奶

奶泡、摩卡(有东西来我在加,只要他是饮料的子类就行)

Head First设计模式读书笔记插图3

实现:

/**
 * 饮料超类
 */
public abstract class Beverage {

    private String description;

    public String getDescription() {
        return description;
    }

    public Beverage() {
    }

    public Beverage(String description) {
        this.description = description;
    }

    /**
     * 获取商品总价的方法
     *
     * @return
     */
    public abstract double cost();

}

public class Espresso extends Beverage {

    /**
     * 获取商品总价的方法
     *
     * @return
     */
    @Override
    public double cost() {
        return 1.99;
    }

    public Espresso() {
        super("浓缩咖啡");
    }
}

/**
 * 装饰者的超类
 */
public abstract class CondimentDecorator extends Beverage {

    protected Beverage beverage;

    public CondimentDecorator(Beverage beverage) {
        this.beverage = beverage;
    }

    // 子类可以将父类的方法变成抽象方法
    public abstract String getDescription();
}

public class Mocha extends CondimentDecorator {

    public Mocha(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ",摩卡";
    }

    /**
     * 获取商品总价的方法
     *
     * @return
     */
    @Override
    public double cost() {
        return beverage.cost() + 0.2;
    }

    public static void main(String[] args) {
        // 摩卡咖啡的描述和价格
        Beverage beverage = new Espresso();
        beverage = new Mocha(beverage);

        System.out.println("beverage.getDescription() = " + beverage.getDescription());
        System.out.println("beverage.cost() = " + beverage.cost());
    }
}

P98,学完工厂和生成器模式看看

什么时候使用装饰者模式?

  1. 动态的为对象增加新的功能
  2. 扩展的功能对一系列类(继承同一个接口/类)都可以复用的

https://www.runoob.com/design-pattern/decorator-pattern.html

四、工厂模式

什么时候使用工厂模式?

  1. 创建对象的过程很复杂
  2. 多个地方再用

4.1 简单工厂

封装对象的创建过程,通过参数的不同创建不同的对象。如果实现改变只需要改这个类就行了。

4.2 工厂方法模式

定义一个工厂接口,为每一个商品提供一个工厂,我觉得书上写的这种方式是一个特例:

public abstract class PizzaStore {

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

其中Spring的也是用的这样的方法:

public abstract class AbstractFactoryBean<T> implements FactoryBean<T>, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean {

    public final T getObject() throws Exception {
        if (this.isSingleton()) {
            return this.initialized ? this.singletonInstance : this.getEarlySingletonInstance();
        } else {
            return this.createInstance();
        }
    }

    protected abstract T createInstance() throws Exception;
}

4.3 抽象工厂方法

我感觉就是生产一个产品族

设计原则5:依赖倒置原则(让高层组件依赖于底层组件的抽象)

Head First设计模式读书笔记插图4

五、单例模式

六、命令模式

一个万能遥控器要操控多个东西,例如:开关灯、开关门。。。

但是有一个问题,那就是这些东西都不是一个类型的(没有实现同一个接口),我们就需要像下面这样进行硬编码:

public class NoCommandRemoteControl {

    public void onClick(int slot) {

        if (slot == 1) {
            Light light = new Light();
            light.on();
        } else if (xxx) {
            Door door = new Door();
            door.open();
        } ...
    }
}

这就导致了行为的发送者和接受者完全耦合起来了,我们需要的设计应该是我们并不知道是谁给我们干活,只要有一个抽象就行了。

定义:

将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction,可撤回)模式。

Head First设计模式读书笔记插图5

实例:

public interface Command {
    void execute();
}

/**
 * 开灯命令
 */
public class LightOnCommand implements Command {

    private Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.on();
    }
}

/**
 * 管理命令的对象,命令的保存+执行
 */
public class RemoteControl {

    Command[] commands;

    public RemoteControl() {
        // 遥控器上有14个按钮
        commands = new Command[14];

        // 为了避免运行的时候判空,赋予默认的什么都不干的命令
        Command noCommand = new NoCommand();
        for (int i = 0; i < 14; i++) {
            commands[i] = noCommand;
        }
    }

    /**
     * 用于客户端动态添加/修改不同插槽的命令
     *
     * @param slot
     * @param command
     */
    public void setCommand(int slot, Command command) {
        commands[slot] = command;
    }

    /**
     * 当按钮按下时调用
     *
     * @param slot
     */
    public void pushDownButton(int slot) {
        // if (commands[slot] != null) {  使用NoCommand就可以避免判空。
        commands[slot].execute();
    }


    // 客户端
    public static void main(String[] args) {

        RemoteControl remoteControl = new RemoteControl();
        remoteControl.setCommand(1, new LightOnCommand(new Light()));   // 前两句相当于:Thread thread = new Thread(() -> {});
        // remoteControl.setCommand(2, new XXXCommand(new XXX()));
        // ...
        remoteControl.pushDownButton(1);    // 相当于:thread.run()
    }
}

撤回命令和执行宏命令,看书

使用场景:

  1. 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
  2. 系统需要在不同的时间指定请求、将请求排队和执行请求。(书上有,没看懂)
  3. 系统需要支持命令的撤销(Undo)操作恢复(Redo)操作(书上说的日志记录)。
  4. 系统需要将一组操作组合在一起,即支持宏命令

策略模式与命令模式区别

策略模式是通过不同的算法做同一件事情:例如排序

而命令模式则是通过不同的命令做不同的事情,常含有(关联)接收者。

七、适配器模式和外观模式

适配器模式:在不修改原有带啊吗的基础上,让原有代码实现另一个接口(转换原有类的类型)

前提:客户是和接口绑定起来而不是实现。

例如:Log4J等日志框架由于未实现相同接口,但又想要让他们之间可以动态的切换,于是使用了适配器模式。

分为类适配器(使用继承)和对象适配器(使用组合)

外观模式:

设计原则5:最少知识原则

不要让太多的类耦合在一起,避免修改系统中的一部分会影响到其它部分。

在一个对象中,我们只应该调用以下范围对象的方法:

  1. 对象本身的方法
  2. 作为方法参数传递进来的对象
  3. 当前方法自己创建的对象
  4. 对象的组件(实例变量)
public class Emailer {
    
    public Emailer(Server server) {…}

    public void sendSupportEmail(String message, String toAddress) {
        EmailSystem emailSystem = server.getEmailSystem();
        String fromAddress = emailSystem.getFromAddress();
        emailSystem.getSenderSubsystem().send(fromAddress, toAddress, message);
    }

}

我们经常会写出这样的代码,有以下几个问题:

1、复杂而且看起来不必要。Emailer 与多个它可能**不是真的需要的 API 进行交互**,例如 EmailSystem,我们获取EmailSystem的目的是获取Sender,那为啥不直接获取Sender呢。

2、**依赖于 Server 和 EmailSystem 的内部结构**,如果两者之一进行了修改,则 Emailer 有可能被破坏。

3、不能重用。任何其他的 Server 实现也必须包含一个能返回 EmailSystem 的 API。


// 减少依赖,直接将sender传入
public Emailer(Sender sender, String fromAddress) {…}

public void sendSupportEmail(String message, String toAddress) {
    sender.send(fromAddress, toAddress, message);
}


// 拒绝
public float getTemp() {
    // 如果Thermometer对象修改我们就要改
    Thermometer th = station.getThermometer();
    return th.getTemp();
}

public float getTemp() {
    return station.getTemp();
}

https://www.cnblogs.com/gaochundong/p/least_knowledge_principle.html

八、模板方法模式

咖啡和茶有着相同的冲泡过程,所以我们可以将它们抽取出来,让他们共同继承一个抽象类:

/**
 * 咖啡因饮料抽象类
 */
public abstract class CaffeineBeverage {

    /**
     * 准备饮料(模板方法)
     */
    public final void prepareRecipe() {
        this.boilWater();
        this.brew();
        this.pourInCup();
        this.addCondiments();
    }

    // 烧热水
    protected void boilWater() {
        System.out.println("烧热水中~");
    }

    // 煮
    protected abstract void brew();

    // 饮料倒入杯中
    protected void pourInCup() {
        System.out.println("饮料倒入杯中~");
    }

    // 加调料
    protected abstract void addCondiments();

    // 钩子函数:在整个程序的生命周期中,提供一个可以在指定时机做某事的东西,例如AOP和析构函数
    // 一般是空方法,子类可以选择不重写;在这里做的是判断客户是否加调料
    protected boolean customerWantsCondiments() {
        return false;
    }
}

public class Tea extends CaffeineBeverage {

    @Override
    protected void brew() {
        System.out.println("煮茶中~");
    }

    @Override
    protected void addCondiments() {
        System.out.println("加柠檬~");
    }

    public static void main(String[] args) {
        CaffeineBeverage caffeineBeverage = new Tea();
        caffeineBeverage.prepareRecipe();
    }

}

定义:

在一个方法中定义一个算法的骨架,部分方法交由子类实现。

钩子函数作用:

  1. 让子类实现算法中可选的部分(如上)
  2. 让子类有机会对模板方法中某些即将发生的步骤作出反应(如析构函数)。

好莱坞原则:高层组件调用底层组件,底层组件不要调用高层组件

在模板方法中,高层组件就是父类,底层组件就是它的子类,整个流程中都是父类调用子类的东西。

也就是说,在我们的开发中最好不要使用super.xxx()调用父类的东西,主要是为了避免高低组件之间有明显的环状依赖。

策略模式与模板方法的区别,能不能结合?

策略模式是将算法抽离出来(使他可以动态切换),并且实现了整个算法,使用者(Duck)通过组合的方式进行调用。

模板方法模式是在父类定义好算法流程,让子类实现。

两者不能结合,因为策略模式把算法抽离出来了,必须通过组合的方式才能提供功能,但是模板方法需要从父类继承下来。而且策略的父类是策略不能让具体的对象(Duck)继承它(Duck不是策略的子类)

九、迭代器和组合模式

迭代器模式定义:

这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。

使用:

public class Waitress {

    Menu pancakeHouseMenu;
    Menu dinerMenu;

    public Waitress(Menu pancakeHouseMenu, Menu dinerMenu) {
        this.pancakeHouseMenu = pancakeHouseMenu;
        this.dinerMenu = dinerMenu;
    }

    public void printMenu() {
        Iterator pancakeIterator = pancakeHouseMenu.createIterator();
        Iterator dinerIterator = dinerMenu.createIterator();

        System.out.println("MENU\n----\nBREAKFAST");
        printMenu(pancakeIterator);
        System.out.println("\nLUNCH");
        printMenu(dinerIterator);
    }

    private void printMenu(Iterator<MenuItem> iterator) {
        while (iterator.hasNext()) {
            MenuItem menuItem = iterator.next();
            System.out.print(menuItem.getName() + ", ");
            System.out.print(menuItem.getPrice() + " -- ");
            System.out.println(menuItem.getDescription());
        }
    }


    public static void main(String[] args) {
        new Waitress(new DinerMenu(), new PancakeHouseMenu()).printMenu();
    }
}

现在晚餐菜单中想要再包含一个菜单,如下:

dinerMenu:[menuItem1, menuItem2, 甜点菜单]

由于类型不同,所以在使用的时候需要用instance of进行判断类型,这样就对客户端(Waitress)不具备透明性了,还是在面向具体的实现编程,所以我们需要采用组合模式;

设计原则8: 单一职责,一个类只负责一个职责

组合模式定义

将对象组合成属性结构来表现层次结构;它可以是客户以一致的方式处理个别对象以及对象的组合

实现:

/**
 * 菜单组件,菜单和菜单项都属于菜单组件
 */
public 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();
    }
}

public class Menu extends MenuComponent {

    private String name;
    private String description;

    // 里面可以装菜单也可以装菜单项
    private List<MenuComponent> menuComponents = new ArrayList<>();

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public String getDescription() {
        return this.description;
    }

    // 菜单独有
    @Override
    public void add(MenuComponent menuComponent) {
        menuComponents.add(menuComponent);
    }

    // 菜单独有
    @Override
    public void remove(MenuComponent menuComponent) {
        menuComponents.remove(menuComponent);
    }

    // 菜单独有
    @Override
    public MenuComponent getChild(int i) {
        return menuComponents.get(i);
    }

    @Override
    public void print() {
        for (MenuComponent menuComponent : menuComponents) {
            // 如果遇到menuComponent,相当于递归调用当前方法。所以可以使用组合模式代替递归
            menuComponent.print();
        }
    }

    public Menu(String name, String description) {
        this.name = name;
        this.description = description;
    }
}

public class MenuItem extends MenuComponent {

    private String name;
    private String description;
    private boolean vegetarian;
    private double price;

    public MenuItem(String name, String description, boolean vegetarian, double price) {
        this.name = name;
        this.description = description;
        this.vegetarian = vegetarian;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public String getDescription() {
        return description;
    }

    // 菜单项独有
    public boolean isVegetarian() {
        return vegetarian;
    }

    // 菜单项独有
    public double getPrice() {
        return price;
    }

    @Override
    public void print() {
        System.out.println(this);
    }

    @Override
    public String toString() {
        return "MenuItem{" +
                "name='" + name + '\'' +
                ", description='" + description + '\'' +
                ", vegetarian=" + vegetarian +
                ", price=" + price +
                '}';
    }
}

public class Waitress {

    private MenuComponent menuComponent;

    public Waitress(MenuComponent menuComponent) {
        this.menuComponent = menuComponent;
    }

    public void printMenu() {
        menuComponent.print();
    }

    public static void main(String[] args) {

        MenuComponent menu = new Menu("所有菜单", "所有菜单");

        // Menu可以修改成各个餐厅的子类(DinerMenu,PancakeHouseMenu)
        Menu menu1 = new Menu("早餐菜单", "早餐菜单");
        menu1.add(new MenuItem("沙拉", "沙拉", true, 12.5));

        Menu menu2 = new Menu("零食", "零食");
        menu2.add(new MenuItem("薯片", "薯片", false, 2.5));
        menu1.add(menu2);

        menu.add(menu1);

        /*
           所有菜单 -|
                   早餐菜单 -|
                          沙拉
                          零食菜单 -|
                                 薯片

         */
        new Waitress(menu).printMenu();
    }

}

组合模式违反了单一职责,它既要管理层次结构,还要执行菜单的操作。

组合模式舍弃了单一职责换取了透明性,组件接口同时包含了管理子节点叶子节点的操作,客户就可以将两种节点类型(菜单、菜单项)一视同仁。

组合模式可以在层次结构已经建立好了的时候代替递归。构建的时候还是要使用递归的。

十、状态模式

在生命周期中,具有很多的状态,我们就可以将状态(变化的部分)抽取出来。

定义:

允许对象在内部状态改变时改变它的行为(封装状态为一个类,动态切换,context类负责调用)。

实现:

/**
 * 不同阶段的状态接口
 */
public interface State {

    /**
     * 投25美分
     */
    void insertQuarter();

    /**
     * 退出25美分
     */
    void ejectQuarter();

    /**
     * 转动把手
     */
    void turnCrank();

    /**
     * 发放糖果
     */
    void dispense();

//    /**
//     * 添加糖果
//     */
//    void refill();
}

/**
 * 没有25美分的状态
 */
public class NoQuarterState implements State {
    GumballMachine gumballMachine;

    public NoQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    public void insertQuarter() {
        System.out.println("已经插入25美分");
        // 修改糖果机的状态为【已经插入25美分】
        gumballMachine.setState(gumballMachine.getHasQuarterState());
    }

    public void ejectQuarter() {
        System.out.println("还没插入25美分");
    }

    public void turnCrank() {
        System.out.println("还没插入25美分");
    }

    public void dispense() {
        System.out.println("还没插入25美分");
    }
}

public class GumballMachine {

    // 卖光状态
    State soldOutState;
    // 没有25美分状态
    State noQuarterState;
    // 已有25美分状态
    State hasQuarterState;
    // 出糖状态
    State soldState;

    State state;
    int count = 0;

    public GumballMachine(int numberGumballs) {

        // 初始化糖果机的几种状态并设置初始状态
        soldOutState = new SoldOutState(this);
        noQuarterState = new NoQuarterState(this);
        hasQuarterState = new HasQuarterState(this);
        soldState = new SoldState(this);

        this.count = numberGumballs;
        if (numberGumballs > 0) {
            state = noQuarterState;
        } else {
            state = soldOutState;
        }
    }

    public void insertQuarter() {
        state.insertQuarter();
    }

    public void ejectQuarter() {
        state.ejectQuarter();
    }

    public void turnCrank() {
        // 转动
        state.turnCrank();
        // 出糖
        state.dispense();
    }

    void releaseBall() {
        System.out.println("A gumball comes rolling out the slot...");
        if (count != 0) {
            count = count - 1;
        }
    }

    int getCount() {
        return count;
    }

    void setState(State state) {
        this.state = state;
    }

    public State getState() {
        return state;
    }

    public State getSoldOutState() {
        return soldOutState;
    }

    public State getNoQuarterState() {
        return noQuarterState;
    }

    public State getHasQuarterState() {
        return hasQuarterState;
    }

    public State getSoldState() {
        return soldState;
    }

    public static void main(String[] args) {
        GumballMachine gumballMachine = new GumballMachine(100);

        gumballMachine.insertQuarter();
        gumballMachine.turnCrank();
    }
}

上面的Demo吧状态切换放到了状态类中,也可以放到Context中进行控制转换的流向,一般状态转换固定时在Context中控制。

与策略模式区别:

状态模式,在软件运行过程中自动的按照规定的流程主动切换,客户端浑然不知。

策略模式,一般是客户主动指定策略。

可以这样想,状态对用户不可见,内部自己改变的,策略对用户可见,而且是由客户切换的。

而且状态模式类似于一个流程性的东西。

十一、代理模式

作用:控制和管理访问

定义:

为另一个对象提供一个替身或占位符以控制对这个对象的访问。

MVC模式

书上写的很好,看书

视图:用来呈现模型

控制器:取得用户输入解读输入对模型的意思

模型:负责维护所有的数据、状态和应用逻辑

控制器将视图上的动作转成模型上的动作;模型实现了应用逻辑。

模式:在某情景(context)下,针对某问题的某种解决方案。

赞(0) 打赏
未经允许不得转载:IDEA激活码 » Head First设计模式读书笔记

一个分享Java & Python知识的社区