常见的设计模式

策略模式

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

设计原则

多用组合,少用继承。

例子:我们有一个duck(鸭子)体系时,并不是所有的鸭子都会叫/飞。那么我们在设计这个类对象时,就不能使用继承方式来继承鸭子的叫(call)或者飞(fly),

因为如果使用继承方式,那么我们创建出来的所有鸭子子类都会继承父类的这些方法(叫/飞)。

这时候我们就需要使用策略模式。

把鸭子共同的属性(颜色…)、方法,放到一个超类,不同的行为,不在超类中进行具体实现,而是在超类中存放一个接口或者抽象类,来作为当前对象的属性,

这样我们在创建鸭子对象时,就可以指定当前对象(鸭子)的属性(叫/飞)。

具体实现:

/**
 * 鸭子父类
 */
public class Duck {
    String name; // 名字
    String color; // 颜色

    // 叫行为,接口
    QuackBehavior quackBehavior;
    //  飞行为,接口
    FlyBehavior flyBehavior;

}
// 叫的父接口
public interface QuackBehavior {
    void quack();
}
// 飞 父接口
public interface FlyBehavior {
    void fly();
}
// 飞 具体实现类
public class FlyNoWay implements FlyBehavior{
    @Override
    public void fly() {
        System.out.println("模板鸭子,不会飞。");
    }
}
// 叫 具体实现类
public class QuackBehavior1 implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("模板鸭子,不会叫");
    }
}

下面是整个的类结构,鸭子继承Duck,飞行行为实现FlyBehavior接口,呱呱叫行为实现QuackBehavior接口。

我们描述事情的方式也稍有改变,不再把鸭子的行为说成是“一组行为”,我们开始把行为想成是“一族算法”


观察者模式

观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会受到并自动更新。

在这里插入图片描述

在主题(被观察者)内部存放一个集合用存放所有观察者,

添加观察者:将观察者放入集合中

移除观察者:将观察者从集合中取出

当主题的属性值发生变化,就通知所有观察者(遍历集合,调用集合中所有观察者的统一方法,所有观察者都必须实现接口中带有的方法。)

所有观察者都必须实现一个通知接口,当主题属性值发生变化时调用。

例子:

// 主题   被监视对象实现
public interface Subject {
    void registerObserver(Observer o);  // 添加一个观察者
    void removeObserver(Observer o);   // 移除一个观察者
    void notifyObserver();   // 当属性发生变化时调用
}
// 所有观察者都需要实现 这个接口
public interface Observer {
    void update(Object o);
}
// 主题
public class WeatherData implements Subject {
    private Integer a;  // 属性值
    private ArrayList observers;  // 观察记录者们

    public WeatherData() {
        observers=new ArrayList();
    }

    @Override
    public void registerObserver(Observer o) {
        // 注册一个监视者
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        // 移除一个监视者
        int i = observers.indexOf(o);
        if(i>=0){
            observers.remove(o);
        }
    }

    @Override
    public void notifyObserver() {
        // 属性值发生改变 调用所有 注册为当前主题的监视者的update方法
        for (int i = 0; i < observers.size(); i++) {
            Observer o = (Observer)observers.get(i);
            o.update(a);
        }
    }

    public Integer getA() {
        return a;
    }

    public void setA(Integer a) {
        this.a = a;
        // a属性值发生改变 通知观察者
        notifyObserver();
    }
}
// 观察者1
public class Observer1 implements Observer {
    @Override
    public void update(Object o) {
        System.out.println("Observer1 看到主题的属性值发生改变,改变后为:"+o);
    }
}
// 观察者2
public class Observer2 implements Observer {
    @Override
    public void update(Object o) {
        System.out.println("Observer2 看到主题的属性值发生改变,改变后为:"+o);
    }
}

思路:

在被观察者中放入一个集合,用来存放观察者,然后被观察者属性修改时,调用一个遍历集合,调用集合中公共的方法,此方法由所有观察者实现一个接口来实现。

如何设计当属性修改超过一定数值才去通知观察者。

在修改属性时,进行判断,后把遍历时提取出一个方法,使用changed 来标记属性修改是否超过一定数值,而后在遍历集合时,判断当前changed,遍历后记得修改会来!

设计原则

找出程序中会变化的方法,然后将其和固定不变的方面相分离。

设计原则

针对接口编程,不针对实现编程

设计原则

多用组合,少用继承


装饰者模式

设计原则

类应该对扩展开放,对修改关闭。

装饰者模式

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

利用继承无法完全解决星巴兹的问题,所遇到的问题有:类数量爆炸,设计死板,以及基类加入新的功能并不适用与所有的子类。

所以,在这里要采用不一样的做法:我们要以饮料为主体,然后在运行时以调料来“装饰"(decorate)饮料。比方说,如果顾客想要,摩卡和奶泡深培咖啡,那么要做的是:

1、拿一个深培咖啡(DarkRoast)对象

2、以摩卡(Mocha)对象装饰它

3、以奶泡(Whip)对象装饰它

4、条用cost()方法,并依赖委托(delegate)将调料的价钱加上去

装饰者和被装饰着对象有相同的超类型

你可以用一个或多个装饰者包装一个对象

创建基类(Beverage:饮料)

/**
 * 饮料
 */
public abstract class Beverage {
    String description ="Unknown Beverage"; //  未知的饮料
    // 用来计算饮料的价钱
    public  abstract double  cost();
    // 由每个子类设置,用来描述饮料,列如 “超优深焙咖啡豆”
    public String getDescription(){
        return "饮料";
    };
}

实现Condiment(调料)抽象类,也就是装饰者类:

/**
 * 首先必须让Condition Decorator能够取代Beverage,
 * 所以将Condiment Decorator扩展字Beverage
 */
public abstract class CondimentDecorator extends Beverage{
    // 所有的调料装饰者都必须要重新实现getDescription()方法。
    public abstract String getDescription();

}

创建一些饮料(被装饰者):

/**
 * 首先,让Espresso扩展自Beverage类,因为是一种饮料。
 */
public class Espresso extends Beverage {
    // 为了要设置饮料的描述,我们写了一个构造器,
    // 记住,description实例变量继承自Beverage
    public Espresso(){
        description="Espresso";
    }
    //  最后,需要计算Espresso的价钱,现在不需要管调料的价钱,
    // 直接把Espresso的价格 1.99 返回即可。
    @Override
    public double cost() {
        return 1.99;
    }
}
/**
 * 首先,让HouseBlend扩展自Beverage类,因为是一种饮料。
 */
public class HouseBlend extends Beverage {
    // 为了要设置饮料的描述,我们写了一个构造器,
    // 记住,description实例变量继承自Beverage
    public HouseBlend(){
        description="House Blend Coffee";
    }
    //  这是另一种饮料,做法和Espresso一样,只是把Espresso名称改为“HouseBlend Coffee”
    @Override
    public double cost() {
        return 0.89;
    }
}
/**
 * 首先,让Espresso扩展自Beverage类,因为是一种饮料。
 */
public class DarkRoast extends Beverage {
    // 为了要设置饮料的描述,我们写了一个构造器,
    // 记住,description实例变量继承自Beverage
    public DarkRoast(){
        description="焦炒咖啡";
    }
    //  最后,需要计算 焦炒咖啡 的价钱,现在不需要管调料的价钱,
    // 直接把Espresso的价格 1.99 返回即可。
    @Override
    public double cost() {
        return 1.99;
    }
}

我们已经完成了抽象组件(Beverage),有了具体组件(HouseBlend),也有了抽象装饰者(CondimentDecorator),现在我们来实现具体装饰者:

/**
 * 摩卡是一个装饰者,所以让它扩展自CondimentDecorator。
 */
public class Mocha extends CondimentDecorator {
    /** 要让Mocha 能够引用一个Beverage,做法如下:
     * 1、 用一个实例变量记录饮料,也就是被装饰这者。
     * 2、 想办法让被装饰者(饮料)被记录到实例变量中。这里的做法是:把饮料当做构造器的参数,
     *      再由构造器将次饮料记录在实例变量中。
     */
    Beverage beverage;
    public Mocha(Beverage beverage){
        this.beverage=beverage;
    }
    // 要计算到Mocha 饮料的价钱。首先把调用委托给被装饰对象,以计算价钱,然后再加上Mocha的价钱,得到最后结果。
    @Override
    public double cost() {
        return beverage.cost()+.20;
    }
    // 我们希望叙述不只是描述饮料(例如“DarkRoast”),而是完整地连调料都描述出来(列如”DarkRoast,Mocha“)。
    // 所以首先利用委托的做法,得到一个叙述,然后在其后加上附加的叙述(例如“Mocha”)
    @Override
    public String getDescription() {
        return beverage.getDescription()+",Mocha";
    }
}
/**
 * 奶泡是一个装饰者,所以让它扩展自CondimentDecorator。
 */
public class Whip extends CondimentDecorator {
    Beverage beverage;
    public Whip(Beverage beverage){
        this.beverage=beverage;
    }
    @Override
    public double cost() {
        return 1;
    }

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

供应咖啡(测试代码)

public class StarbuzzCoffee {
    public static void main(String[] args) {
        Beverage beverage =new Espresso();  // 订一杯Espresso, 不需要调料,打印出它的描述和价格

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

        Beverage beverage2 = new DarkRoast();
        beverage2 = new Mocha(beverage2); //用Mocha装饰它
        beverage2 = new Whip(beverage2); //用第二个Mocha装饰它

        System.out.println(beverage2.getDescription()+" $ "+beverage2.cost());
    }
}

思路:被装饰者超类,

调料(装饰者超类),装饰者也能够代替装饰者,所以也需要实现统一的超类,调料还必须重新实现描述方法(Description)

具体:

被装饰者实现被装饰者超类。

装饰者实现装饰者超类,并且在装饰者内部存放被装饰者,构造时就被初始化。

测试:

需要先创建被装饰者,然后根据需求创建被装饰者。


工厂模式

当使用“new”时,你的确是在实例化一个具体类,所以用的确实是实现,而不是接口,已经知道了代码帮着具体类会导致代码哥更脆弱,更缺乏弹性。

Duck duck = new MallardDuck();

要使用接口让代码具有弹性,但是还是得简历具体类的实例!

当用一群相关的具体类时,通常会写出这样的代码:

Duck duck;
//有一大推不同的鸭子类,但是必须等到运行时,才知道该实例化哪一个
if(picnid){
	duck = new MallardUck(); 
}else if (hunting){
	duck = new DecoyDuck();
}else if(inBathTub){
	duck = new RubberDuck();
}

针对接口编程,可以隔离掉以后系统可能发生的一大堆改变,如果代码是针对接口而写,那么通过多态,它可以与任何新类实现该接口。但是,当代码使用大量的具体时,等于是自找麻烦,因为一旦加入新的具体类,就必须改变代码。也就是说,你的代码并非”对修改关闭“。想用新的具体类型来扩展代码,必须重新打开它。

​ 找出会变化的方面,把它们从不变的部分分离出来。

工厂(factory)处理创建对象的细节,一旦有了SimplePizzaFactory,orderPizza()就变成此对象的客户。当需要比萨时,就要比萨工厂做一个。那些orderPizza()方法需要知道希腊比萨或者哈利比萨的日志一区不复返了。现在orderPizza()方法只关心从工厂的到了一个比萨,而这个比萨实现了Pizza接口,所以它可以调用prepare(),bake(),cut(),box()来分别进行准备、烘烤、切片、装盒。

披萨超类(或者是接口)

/**
 * 披萨类
 */
public class Pizza {
    /**
     * 装盒子
     */
    public void box() { }
    /**
     * 切
     */
    public void cut() { }
    /**
     * 烘烤
     */
    public void bake() {  }
    /**
     * 准备
     */
    public void prepare() {}
}

具体披萨、、、后续都省略

/**
 * 蛤厉比萨
 */
public class ClamPizza extends Pizza {
}

披萨工厂

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

披萨商店:(整体思路)

public class PizzaStore {
    //无参构造 只能创建默认的披萨
    public Pizza orderPizza(){
        Pizza pizza=new Pizza();
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }
    // 将披萨类型传入创建不同的披萨
    Pizza orderPizza(String type){
        Pizza pizza = null;
        /**
         * 代码“没有”对修改封闭,如果比萨店改变它所供应的比萨风味,就得进到这里来修改
         * 这是变化的部分,随着时间过去,披萨菜单改变,这里就必须一改再改
         */

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

    /**
     * 现在最好将创建对象移到orderPizza()之外,
     *
     */
    Pizza orderPizza1(String type){
        Pizza pizza = null;
        // 首先,把创建对象的代码从orderPizza()方法中抽离
        // 这里我们创建一个新对象 称为“工厂”
        /**
         * 工厂(factory)处理创建对象的细节,一旦有了SimplePizzaFactory,
         * orderPizza()就变成此对象的客户。当需要比萨时,就要比萨工厂做一个。
         * 那些orderPizza()方法需要知道希腊比萨或者哈利比萨的日志一区不复返了。
         * 现在orderPizza()方法只关心从工厂的到了一个比萨,而这个比萨实现了Pizza接口,
         * 所以它可以调用prepare(),bake(),cut(),box()来分别进行准备、烘烤、切片、装盒。
         */

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }

    /**
     * 修改我们的代码,使用工厂来为我们创建比萨
     */
    SimplePizzaFactory factory;

    /**
     * Pizza构造器,需要一个工厂作为参数
     */
    public PizzaStore(SimplePizzaFactory factory){
        this.factory=factory;
    }

    /**
     * 而orderPizza()方法通过简单传入订单类型来使用工厂创建比萨
     */
    public Pizza orderPizzaFactory(String type){
        Pizza pizza;

        pizza=factory.createPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

加盟店可能想要提供不同风味的披萨(比方说纽约、芝加哥、加州),

你希望加盟店都能利用你的代码,好比披萨的流程一致不变,

其中一家加盟店希望工厂能制造纽约风味的披萨;薄饼,美味的酱料和少量的芝士。

另一家加盟店希望工厂能制造芝加哥风味的披萨,他们的顾客想要薄饼,重味的酱料和大量的芝士。

如果利用SimplePizzaFactory,写出三种不同的工厂,分别是NYPizzaFactory、ChicagoPizzaFactory、CaliforniaPizzaFactory,那么各地加盟店都有适合的工厂可以使用。

//这里创建的工厂,是制造纽约风味的披萨
NYPizzaFactory nyFactory = new NYPizzaFactory();
// 然后建立一个披萨店,将纽约工厂的引用作为参数
PizzaStore nyStore = new PizzaStore(nyFactory);
// 当我们制造披萨,会得到纽约风味的披萨
nyStore.order("Veggie");

在推广SimpleFactory时,你发现加盟店的确是采用你的工厂创建披萨,但是其他部分,却开始采用他们自创的流程,烘烤的做法有些差异,不要切片、使用其他厂商的盒子。

你希望能够创建一个框架,把加盟店和创建披萨捆绑在一起的同时又保持一定的弹性。

所要做的事情,就是把createPizza()方法放回到PizzaStore中,不过要把它设置成“抽象方法”,然后喂每个区域风味常见一个PizzaStore的子类。

public abstract class PizzaStore1 {
    public Pizza orderPizza(String type){
        Pizza pizza;
        // 现在createPizza()方法从工厂对象中移回PizzaStore
        pizza = createPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
    // 现在把工厂对象移到这个方法中,每个子类各自决定如果制造披萨
    abstract Pizza createPizza(String type);
}

工厂模式:

所有工厂模式都用来封装对象的创建。工厂方法模式(Factory method Pattern)通过子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的。

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

设计原则

要依赖抽象,不要依赖具体类

抽象工厂模式

提供了一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

单例模式(单件模式)

确保一个类只有一个实例,并一个全局访问点。

/**
 * 经典的单例模式实现
 */
public class Singleton {
    private static Singleton uniqueInstance;
    // 私有化无参构造
    private Singleton (){};
    // 使用getInstance方法来创建对象
    public static Singleton getInstance(){
        if(uniqueInstance ==null){
            uniqueInstance=new Singleton();
        }
        return uniqueInstance;
    };
    // 但是多线程情况下容易出现两个线程同时进来的情况,这时候就会创建两个对象
    // 修改
    // 通过在方法前面添加synchronized关键字,迫使每个线程在进入这个方法之前,要先等别的线程离开。
    public static synchronized Singleton getInstance1(){
        if(uniqueInstance ==null){
            uniqueInstance=new Singleton();
        }
        return uniqueInstance;
    };
    // 这样解决了多线程情况下 创建多个情况,但是我们只有第一次进来才会创建对象,后续都不需要,使用同步的话会大大降低效率
    // 1、 如果getInstance()的性能对应用程序不是很关键,那就什么都别做
    // 2、使用“急切”创建实例,而不用延迟实例化的做法 (恶汉模式)
    // 可以添加一个静态变量在对象被加载的时候就创建对象,在使用时只需要条用就行,但是当类被加载但是没有被使用的时候就会出现额外的开销
    private static Singleton uniqueInstance1=new Singleton();
    public static Singleton getInstance2(){
        return uniqueInstance1;
    }
    // 3、用“双重检查加锁“,在getInstance()中减少使用同步
    private volatile static Singleton uniqueInstance2;
    // 私有化无参构造

    public static Singleton getInstance3(){
        if(uniqueInstance2==null){
            synchronized (Singleton.class){
                if(uniqueInstance2==null){
                    uniqueInstance2=new Singleton();
                }
            }
        }
        return uniqueInstance2;
    }

}

命令模式

把方法调用(method invocation)封装起来。

命令模式:

将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持课撤销的操作。

思想:命令对象通过在特定接受者上绑定一组动作来封装一个请求。要达到这一点,命令对象将动作和接受者包进对象中。这个对象只暴露出一个execute()方法,当此方法被调用的时候,接受者就会进行这些动作。从外面来看,其他对象不知道究竟哪个接受者进行了那些动作,只知道如果调用execute()方法,请求的目的就能达到。

/**
 * 命令接口
 */
public interface Command {
    void execute();
    void undo();
}
/**
 * 遥控器
 */
public class RemoteControl {
    Command[] onCommands;
    Command[] offCommands;
    public  RemoteControl(){
        onCommands =new Command[7];
        offCommands =new Command[7];
//        Command noCommand = (Command) new NoCommand();
//        for (int i=0;i<7;i++){
//            onCommands[i]=noCommand;
//            offCommands[i]=noCommand;
//        }
    }
    public void setCommand(int slot,Command onCommand,Command offCommand){
        onCommands[slot]=onCommand;
        offCommands[slot]=offCommand;
    }
    public void onButtonWasPushed(int slot){
        onCommands[slot].execute();
    }
    public void offButtonWasPushed(int slot){
        onCommands[slot].execute();
    }

    /**
     * 覆盖toString(),打印出每个插槽和它对应的命令,稍后在测试遥控器的时候,会用到这个方法
     * @return
     */
    @Override
    public String toString() {
        StringBuffer stringBuff= new StringBuffer();
        stringBuff.append("\n------------------Remote Control  --------------\n");
        for(int i =0 ;i<onCommands.length;i++){
            stringBuff.append("[solt"+i+"]"+onCommands[i].getClass().getName()+
                    "      "+offCommands[i].getClass().getName() + "\n");
        }
        return stringBuff.toString();
    }
}
/**
 * 音响
 */
public class Stereo {
    // 状态 ,播放、暂定
    boolean flag=false;
    // 音量
    int volume=0;
    // 播放
    public void on(){
        flag=true;
    }
    public void off(){
        flag=false;
    }
    // 设置为CD播放
    public void setCD(){

    }
    // 设置音量
    public void setVolume(int volume) {
        this.volume = volume;
    }
}
/**
 * 音响包装类 
 */
public class StereoOnWithCDCommand implements Command {

    Stereo stereo;
    public StereoOnWithCDCommand(Stereo stereo){
        this.stereo=stereo;
    }
    @Override
    public void execute() {
        stereo.on();
        stereo.setCD();
        stereo.setVolume(11);
        System.out.println("开始播放 ");
    }

    @Override
    public void undo() {
        if(stereo.flag){
            stereo.off();
            System.out.println("暂停播放 ");
        }
    }
}
/**
 * 灯类
 */
public class Light {

    //灯的当前状态  默认为false关闭
    boolean flag = false;

    public void off(){
        flag=false;
    }
    public void on(){
        flag=true;
    }
}
/**
 * 灯包装类
 */
public class LightOffCommand implements Command {

    Light light;
    public LightOffCommand(Light light){
        this.light=light;
    }
    // 开灯、关灯
    @Override
    public void execute() {
        light.on();
        System.out.println("开灯!");
    }
    // 关灯
    @Override
    public void undo() {
        if (light.flag) {
            light.off();
            System.out.println("关灯");
        }
    }
}
/**
 * 测试类
 */
public class RemoteLoader {
    public static void main(String[] args) {
        RemoteControl remoteControl=new RemoteControl();
        Light light=new Light();
        LightOffCommand lightOnCommand=new LightOffCommand(light);
        LightOffCommand lightOffCommand=new LightOffCommand(light);
        Stereo stereo=new Stereo();
        StereoOnWithCDCommand stereoOnWithCDCommand=new StereoOnWithCDCommand(stereo);
        StereoOnWithCDCommand stereoOffWithCDCommand=new StereoOnWithCDCommand(stereo);
        remoteControl.setCommand(0,lightOnCommand,lightOffCommand);
        remoteControl.setCommand(1,stereoOnWithCDCommand,stereoOffWithCDCommand);
        remoteControl.onButtonWasPushed(0);
        remoteControl.onButtonWasPushed(1);
    }
}

命令模式将发出请求的对象和执行请求的对象解耦。

在被解耦的两者之间是通过命令对象进行沟通的,命令对象封装了接受者和一个或一组动作。

调用者通过调用命令对象的execute()发出请求。这回使得接受者的动作被调用。

调用者可以接受命令当做参数,甚至在运动时动态的进行。

适配器模式与外观模式

装饰者模式,将对象包装起来,赋予它们新的职责,而现在则是义不同的目的,包装某些对象:让它们的接口看起来不像自己而像是别的东西。

为何要这样做?因为这样就可以在设计中,将类的接口转换成想要的接口,以便实现不同的接口。

在这里插入图片描述

面向对象适配器:

假设已有一个软件系统,你希望它能和一个新的厂商类库搭配使用,但是这个新的厂商所设计出来的接口,不同于旧厂商的接口:

在这里插入图片描述

你不想改变现有的代码,解决这个问题(而且你也不能改变厂商的代码)。

你可以写一个类,将新厂商接口转接成你所期望的接口。

在这里插入图片描述

例子:

/**
 * 简化鸭子接口
 */
public interface Duck {

    void quack();
    void fly();
}
/**
 * 鸭子类
 */
public class MallardDuck implements Duck {

    @Override
    public void quack() {
        System.out.println("Quack!");
    }

    @Override
    public void fly() {
        System.out.println("I'm flying");
    }
}
/**
 * 火鸡
 */
public interface Turkey {
    // 火鸡不会呱呱叫。只会咯咯(gobble)叫
    void gobble();
    // 火鸡会飞,虽然飞不远
    void fly();
}
/**
 * 火鸡类
 */
public class WildTurkey implements Turkey {
    @Override
    public void gobble() {
        System.out.println("Gobble gobble");
    }

    @Override
    public void fly() {
        System.out.println("I'm flying a short distance");
    }
}
/**
 * 火鸡适配器
 */
public class TurkeyAdapter implements Duck {
    Turkey turkey;
    // 需要取得要适配的对象引用,这里我们利用构造器取得这个引用
    public TurkeyAdapter(Turkey  turkey){
        this.turkey=turkey;
    }
    @Override
    public void quack() {
        // 现在我们需要实现接口中所有的方法。quack()在类之间的转换很简单,只要调用gobble()就可以了。
        turkey.gobble();
    }

    //火鸡的飞行距离很短,不像鸭子可以长途飞行。要让鸭子的飞行和火鸡的飞行能够对应,必须联系五次调用火鸡的fly()来完成。
    @Override
    public void fly() {
        for (int i = 0; i < 5; i++) {
            turkey.fly();
        }
    }
}
//测试类
public class DuckTestDrive {
    public static void main(String[] args) {
        MallardDuck duck =new MallardDuck(); //创建一只鸭子
        WildTurkey turkey= new WildTurkey(); //和一只火鸡
        Duck turkeyAdapter =new TurkeyAdapter(turkey); //然后将火鸡包装静一个火鸡适配器中,使它看起来是一只鸭子
        System.out.println("The Turkey says...");
        turkey.gobble();
        turkey.fly();
        System.out.println("\n The Duck says...");
        testDuck(duck);
        System.out.println("\n The TurkeyAdapter says...");
        testDuck(turkeyAdapter);
    }

    private static void testDuck(Duck duck) {
        duck.quack();
        duck.fly();
    }
}

客户使用适配器的过程如下:

1、客户通过目标接口调用适配器的方法对适配器发出请求

2、适配器使用被适配者接口把请求转换成被适配者的一个或者多个调用接口。

3、客户接收到调用的结果,但并未察觉这一切是适配器在起转换作用。

适配器模式:

将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的累可以合作无间。

使用对象组合,以修改的接口包装被适配者,这种做法还有额外的有点,那就是,被适配者的任何子类,都可以匹配着适配器使用。

类适配器

需要多重继承才能够实现它,这在java中是不可能的。但是当你在使用多重继承语言的时候,还是可能遇到这样的需求。

在这里插入图片描述

外观模式

提供了一个统一的接口,用来访问子系统中的一群接口,外观定义了一个高层接口,让子系统更容易使用。

“最少知识”原则

只和你的密友谈话。

把复杂的接口调用,优化出来对外暴露一个接口来调用后续所有接口。

模板方法模式

模板方法

定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。

把模板方法(一个整体流程调用方法)作为超类的方法,把子类中共同的方法放入超类中,不同的方法,在超类中定义为抽象类,由子类提供实现。

模板方法模式

在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

迭代器与组合模式

迭代器

这里有两个集合,分别是数组来实现,和使用ArrayList来实现的集合。

要有一个既可以遍历数组,也可以遍历ArrayList的方法。

例子:

//遍历ArrayList
for(int i =0;<breakItem.size();i++){
	Item item=(Item)breakItem.get(i);
}
//遍历数组
for(int i=0;i<Items.length;i++){
	Item item =(Item) Items[i];
}
// 尝试创建一个迭代器
Iterator iterator = breakItem.createIterator(); // 从breakItem(List集合菜单)中取得一个迭代器
while(iterator.hasNext()){ // 当还有其他项时...
	Item item =(Item)iterator.next(); //取得下一项
}
// 客户只需要调用hasNext()和 next(); 而迭代器会暗中调用ArrayList的get();
// 将它在数组上试试
Iterator iterator = Items.createIterator(); // 从Item(数组菜单)中取得一个迭代器
while(iterator.hasNext()){ // 当还有其他项时...
	Item item =(Item)iterator.next(); //取得下一项
}
// 客户只需要调用hasNext()和 next(); 而迭代器会暗中使用组数的下标

迭代器模式依赖于一个名为迭代器的接口。这是一个可能的迭代器的接口:

public interface Iterator{
	hasNext();  //hasNext()方法告诉我们是否在这个聚合中还有更多的元素
	next(); // next()方法返回这个 聚合中的下一个对象
}

例子:

/**
 * 迭代器接口
 */
public interface Iterator {
    boolean hasNext();
    Object next();
}
/**
 * 使用list 实现菜单
 */
public class PancakeHouseMenu {
    List<MenuItem> list =new ArrayList();

    public Iterator createIterator(){
        return new PancakeHouseMenuIterator(list);
    }
    public void add(MenuItem item){
        list.add(item);
    }
}
/**
 * list集合的迭代器
 */
public class PancakeHouseMenuIterator implements Iterator {
    List<MenuItem> list=new ArrayList<>();
    int position=0;
    public PancakeHouseMenuIterator(List list){
        this.list=list;
    }
    @Override
    public boolean hasNext() {
        if (position >=list.size() || list.get(position)==null ) {
            return false;
        }else {
            return true;
        }
    }

    @Override
    public Object next() {
        MenuItem item =list.get(position);
        position = position+1;
        return item;
    }
}
/**
 * 使用数组实现菜单
 */
public class DinerMenu {
    static final int MAX_ITEMS = 6;
    int numberOfItems = 0;
    MenuItem[] items;

    public MenuItem[] getItems() {
        return items;
    }

    public void setItems(MenuItem[] items) {
        this.items = items;
    }

    public Iterator createIterator(){
        return new DinerMenuIterator(items);
    }
}
/**
 * 数组实现的菜单的迭代器
 */
public class DinerMenuIterator implements Iterator {
    MenuItem[] items;
    int position = 0;
    public  DinerMenuIterator(MenuItem[] items){
        this.items=items;
    }

    @Override
    public boolean hasNext() {
        if(position>=items.length || items[position] == null){
            return false;
        }
        return true;
    }

    @Override
    public Object next() {
        MenuItem item =items[position];
        position = position+1;
        return item;
    }
}
/**
 * 女服务员
 */
public class Waitress {
    DinerMenu dinerMenu; // 使用数组实现的菜单
    PancakeHouseMenu pancakeHouseMenu; //使用list实现的菜单

    public Waitress(DinerMenu dinerMenu,PancakeHouseMenu pancakeHouseMenu){
        this.dinerMenu=dinerMenu;
        this.pancakeHouseMenu=pancakeHouseMenu;
    }
    public void pintMenu(){
        Iterator pancakeIterator= pancakeHouseMenu.createIterator();
        Iterator dinerIterator=dinerMenu.createIterator();
        System.out.println("MENU\n------\nBREAKFAST");
        printMenu(pancakeIterator);
        System.out.println("\n LUNCH");
        printMenu(dinerIterator);
    }

    private void printMenu(Iterator iterator) {
        while (iterator.hasNext()){
            MenuItem item =(MenuItem)iterator.next();
            System.out.println("菜名:"+item.getName()+"  价格为:"+item.getPrice() +"   是否为素材"+ item.isFlag());
        }
    }
}
//测试类
public class Test {
    public static void main(String[] args) {
        // 创建菜单
        MenuItem[] items= new MenuItem[6];
        items[0]=new MenuItem("娃娃菜","10",true);
        items[1]=new MenuItem("西红柿炒蛋","15",true);
        items[2]=new MenuItem("辣椒炒肉","20",false);
        items[3]=new MenuItem("宫保鸡丁","20",false);
        items[4]=new MenuItem("鱼香肉丝","20",false);
        items[5]=new MenuItem("凉皮","15",true);
        DinerMenu dinerMenu=new DinerMenu();
        dinerMenu.setItems(items);
        PancakeHouseMenu pancakeHouseMenu=new PancakeHouseMenu();
        pancakeHouseMenu.add(new MenuItem("苏杭醉蟹","30",false));
        pancakeHouseMenu.add(new MenuItem("冰镇芥兰","17",true));
        pancakeHouseMenu.add(new MenuItem("火腿青菜","15",true));
        pancakeHouseMenu.add(new MenuItem("酸酸糖葫芦","15",true));
        pancakeHouseMenu.add(new MenuItem("干煎青门蟹","25",false));

        // 创建女服务员
        Waitress waitress=new Waitress(dinerMenu,pancakeHouseMenu);
        waitress.pintMenu();
    }
}

迭代器模式

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

组合模式

允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。

让我们以菜单为例思考这一切,这个模式能够创建一个树形结构,在同一个结构中处理嵌套菜单和菜单项组。通过将菜单和项放在相同的结构中,我们创建了一个“整体/部分“层次结构,即由菜单和菜单项组成的对象树。但是可以将它视为一个整体,像是一个丰富的大菜单。

一旦有了丰富的大菜单,我们就可以使用这个模式来“统一处理个别对象和组合对象”。这意味着什么?他意味着,如果我们有了一个树形结构的菜单、子菜单和可能还带有菜单项的子菜单,那么任何一个菜单都是一种“组合”。因为它既可以包含其他菜单,也可以保单菜单项。个别对象只是菜单项——并未持有其他对象。就像你将看到的,使用一个遵照组合模式的设计,让我们能够写出简单的代码,就能够对整体菜单结构应用相同操作(例如打印!)。

在这里插入图片描述

实现菜单组件:

/**
 * 菜单组件提供了一个接口,让菜单项和菜单共同使用。
 * 因为我们希望能够为这些方法提供默认的实现,所以我们在这里使用了一个抽象类。
 */
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();
    }
    public Iterator createIterator(){
        throw new UnsupportedOperationException();
    }
}

实现菜单项:

/**
 * 菜单项覆盖了这些对它有意义的方法,至于那些没有意义的方法(例如add()),
 * 就置之不理,add()之所以没意思,是因为菜单项已经是叶节点,它的下面不能再有任何组件。
 */
public class MenuItem extends MenuComponent {
    String name;
    String description;
    boolean vegetarian;
    double price;
    public MenuItem (String name,String description,boolean vegetarian,double price){
        this.name=name;
        this.description=description;
        this.vegetarian=vegetarian;
        this.price=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 void print(){
        System.out.println("   "+getName());
        if(isVegetarian()){
            System.out.println("(v)");
        }
        System.out.println(", " +getPrice());
        System.out.println("    -- "+getDescription());
    }
    public Iterator createIterator(){
        return new NullIterator();
    }
}

实现组合菜单:

/**
 * 菜单也覆盖了一些对它有意义的方法,例如增加或者删除菜单项(或者其他的菜单!)。
 * 除此之外,我们也使用getName()和getDescription()方法来返回菜单名称与描述
 */
public class Menu extends MenuComponent {
    ArrayList menuComponents =new ArrayList();
    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);
    }

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

    @Override
    public String getDescription() {
        return description;
    }
    /**
     * 因为菜单是一个组合,包含了菜单项和其他的菜单,所以它的print()应该打印出它所包含的一切,
     * 如果不这么做,我们就必须遍历整个组合的每个节点,然后将每一项打印出来。这么一来,也就失去了使用组合结构的意义。
     */

    public void print(){
        System.out.println("\n"+getName());
        System.out.println("\n"+getDescription());
        System.out.println("---------------------");
        /**
         * 我们要做的只是改变print()方法,就让他不仅打印出菜单本身的信息,也打印出菜单内所有组件的内容:其他菜单和菜单项
         */
        Iterator iterator=menuComponents.iterator();
        // 在遍历过程中,可能遇到其他菜单,或者是遇到菜单项,由于菜单和菜单项有实现了print(),那我们只要调用print()即可
        while (iterator.hasNext()){
            MenuComponent menuComponent=(MenuComponent) iterator.next();
            menuComponent.print();
        }

    }
    public Iterator createIterator(){
        return new CompositeIterator(menuComponents.iterator());
    }
}

更新女招待的代码:

public class Waitress {
    MenuComponent allMenus;
    public Waitress(MenuComponent allMenus){
        this.allMenus=allMenus;
    }
    public void printMenu(){
        allMenus.print();
    }
    // 打印素食菜单
    public void printVegetarianMenu(){
        Iterator iterator=allMenus.createIterator();
        System.out.println("\n VEGETARIAN MENU \n---");
        while (iterator.hasNext()){
            MenuComponent menuComponent=(MenuComponent)iterator.next();
            try{
                if(menuComponent.isVegetarian()){
                    menuComponent.print();
                }
            }catch (UnsupportedOperationException e){}
        }
    }
}

编写测试程序:

public class MenuTestDrive {
    public static void main(String[] args) {
        MenuComponent pancakeHouseMenu=new Menu("PANCAKE HOUSE MENU","Breakfast");
        MenuComponent dinerMenu=new Menu("DINER MENU","Lunch");
        MenuComponent cafeMenu=new Menu("CAFE MENU","Dinner");
        MenuComponent dessertMenu=new Menu("DESSERT MENU","Dessert of course");

        MenuComponent allMenus=new Menu("All MENUS","All menus combined");
        allMenus.add(pancakeHouseMenu);
        allMenus.add(dinerMenu);
        allMenus.add(cafeMenu);
        dinerMenu.add(new MenuItem("Pasta","Spagnetti with Marinara Sauce,and a slice of sourdough bread",
                true,3.89));
        dinerMenu.add(dessertMenu);
        dessertMenu.add(new MenuItem("Apple Pie","Apple pie with a flakey crust,topped with vanilla ice cream",
                true,1.59));


        Waitress waitress=new Waitress(allMenus);
//        waitress.printMenu();
        waitress.printVegetarianMenu();
    }
}

状态模式

允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

类图:

在这里插入图片描述

策略模式和状态模式类图一样,但是这两个模式的差别在于它们的“意图”。

以状态模式而言,我们将一群行为封装在状态对象中,context的行为随时可以委托到那些状态对象中的一个。随着时间的流逝,当前状态在状态对象集合中游走改变,以反映出context内部的状态,因此,context的行为也会跟着改变。但是context的客户对于状态对象了解不多,甚至根本是浑然不知。

而以策略模式而言,客户通常主动指定Context所要组合的策略对象是哪一个。现在,固然策略模式让我们具有弹性,能够在运行时改变策略,但是对于某个context对象来说,通常都只有一个最适当的策略对象。

一般来说,我们把策略模式想成是除了继承之外的一种弹性替代方案。如果你使用继承定义了一个类的行为,你将被这个行为困住,甚至要修改它都很难。

有了策略模式,你可以通过组合不同的对象来改变行为。

例:

一个糖果机的多种状态。(未投币状态,已投币状态,转动曲柄状态,发糖状态,售空状态),还有一个游戏。投币后进行抽奖,抽中则出现两个糖果。这个我们后续再添加。

/**
 * 所有状态的顶级接口
 */
public interface State {
    void insertQuarter();
    void ejectQuarter();
    void tumCrank();
    void dispense();
}
/**
 * 买糖果状态
 */
public class SoldState implements State {
    GumballMachine gumballMachine;
    /**
     * 我们通过构造器得到糖果机的引用,然后将它记录在实例变量中。
     */
    public SoldState(GumballMachine gumballMachine){
        this.gumballMachine=gumballMachine;
    }
    @Override
    public void insertQuarter() {
        System.out.println("不能进行投钱!");
    }

    @Override
    public void ejectQuarter() {
        System.out.println("不能进行退钱!");
    }

    @Override
    public void tumCrank() {
        System.out.println("不能转动曲柄!");
    }

    /**
     * 真正的工作在这里
     * 现在是在SoldState状态,也就是说顾客已经付钱了,所以我们首先需要机器发放糖果,
     * 我们问机器糖果的剩余数目是多少,然后将状态转换到NoQuarterState或者SoldOutState状态
     */
    @Override
    public void dispense() {
        gumballMachine.releaseBall();
        if(gumballMachine.getCount() > 0){
            gumballMachine.setState(gumballMachine.getNoQuarterState());
        }else {
            System.out.println("糖果机中已经没有糖果了");
            gumballMachine.setState(gumballMachine.getSoldOutSate());
        }
    }
}
/**
 * 买空
 */
public class SoldOutState implements State {
    GumballMachine gumballMachine;
    /**
     * 我们通过构造器得到糖果机的引用,然后将它记录在实例变量中。
     */
    public SoldOutState(GumballMachine gumballMachine){
        this.gumballMachine=gumballMachine;
    }
    @Override
    public void insertQuarter() {
        System.out.println("不能进行投钱!");
    }

    @Override
    public void ejectQuarter() {
        System.out.println("不能进行退钱!");
    }

    @Override
    public void tumCrank() {
        System.out.println("不能转动曲柄!");
    }

    /**
     * 真正的工作在这里
     * 现在是在SoldState状态,也就是说顾客已经付钱了,所以我们首先需要机器发放糖果,
     * 我们问机器糖果的剩余数目是多少,然后将状态转换到NoQuarterState或者SoldOutState状态
     */
    @Override
    public void dispense() {
        System.out.println("糖果机中没有糖果,请放入糖果!");
    }
}
/**
 * 没有投钱状态
 */
public class NoQuarterState implements State {
    GumballMachine gumballMachine;

    /**
     * 我们通过构造器得到糖果机的引用,然后将它记录在实例变量中。
     */
    public NoQuarterState(GumballMachine gumballMachine){
        this.gumballMachine=gumballMachine;
    }

    /**
     * 如果有人投入了25分钱,我们就打印出一条消息,说明我们接受了25分钱,
     * 然后改变及其的状态到HasQuarterSate
     */
    @Override
    public void insertQuarter() {
        System.out.println("我们接受了25分钱。");
        gumballMachine.setState(gumballMachine.getHasQuarterSate());
    }

    /**
     * 如果没有给钱,就不能要求退钱。
     */
    @Override
    public void ejectQuarter() {
        System.out.println("你没有投钱,不能要求退钱。");
    }

    /**
     * 如果没给钱,就不能要求糖果。
     */
    @Override
    public void tumCrank() {
        System.out.println("没有投钱,不能要求糖果");
    }

    /**
     * 如果没的道歉,我们就不能发放糖果
     */
    @Override
    public void dispense() {
        System.out.println("没有钱,不能发放糖果!");
    }
}
/**
 * 已经投钱状态
 */
public class HasQuarterState implements State {
    GumballMachine gumballMachine;
    /**
     * 我们通过构造器得到糖果机的引用,然后将它记录在实例变量中。
     */
    public HasQuarterState(GumballMachine gumballMachine){
        this.gumballMachine=gumballMachine;
    }

    /**
     * 这是一个对此状态不恰当的动作
     */
    @Override
    public void insertQuarter() {
        System.out.println("你不能再投入25分钱。");
    }

    /**
     * 退出顾客的25分钱,并将状态转换到NoQuarterState状态
     */
    @Override
    public void ejectQuarter() {
        System.out.println("退钱成功!");
        gumballMachine.setState(gumballMachine.getNoQuarterState());
    }

    /**
     * 当曲柄被转动时,我们就调用他的setState()方法,并传入soldState对象作为参数,将机器的状态转换到SoldState状态。
     * 这个SoldState对象可以通过getSoldState()方法取得(每个状态都有一个getter方法)
     */
    @Override
    public void tumCrank() {
        System.out.println("曲柄被转动。。。");
        gumballMachine.setState(gumballMachine.getSoldState());

    }

    @Override
    public void dispense() {
        System.out.println("不能分发糖果!");
    }
}
public class GumballMachine {
    /**
     * 所有的状态都在这里
     */
    State soldOutSate;
    State noQuarterState;
    State hasQuarterSate;
    State soldState;
    State state=soldState;
    int count =0;

    public int getCount() {
        return count;
    }
    // 添加糖果
    public void setCount(int count) {
        this.count = count;
        state=noQuarterState;
    }

    /**
     * 这个count实例变量记录及其内装有多少糖果,
     * 构造器取得糖果的出师数目并把它存放在一个实例变量中。
     */
    public GumballMachine(int count) {
        soldOutSate=new SoldOutState(this);
        noQuarterState=new NoQuarterState(this);
        hasQuarterSate=new HasQuarterState(this);
        soldState=new SoldState(this);
        this.count = count;
        // 如果超过0颗糖果,我们就把状态设置为NoQuarterState,为投钱状态
        if (count>0) {
            state=noQuarterState;
        }
    }
    public void insertQuarter() {
        state.insertQuarter();
    }
    public void ejectQuarter() {
        state.ejectQuarter();

    }

    /**
     *  我们不需要在准备一个dispense()的动作方法,因为这只是一个内部动作,
     *  用于不可以直接要求机器发放糖果,但我们是在状态对象的tumCrank()方法中调用dispense()方法的
     */
    public void tumCrank() {
        state.tumCrank();
        state.dispense();
    }

    /**
     * 这个方法允许其他对象(像我们的状态对象)将机器的状态转换到不同的状态
     */
    public void setState(State state){
        this.state=state;
    }

    /**
     * 这个机器提供了一个releaseBall()的辅助方法来释放出糖果,并将count实例变量的值减1。
     */
    void releaseBall(){
        System.out.println("发放了一个糖果!");
        if(count != 0){
            count= count-1;
        }
    }

    public State getSoldOutSate() {
        return soldOutSate;
    }

    public void setSoldOutSate(State soldOutSate) {
        this.soldOutSate = soldOutSate;
    }

    public State getNoQuarterState() {
        return noQuarterState;
    }

    public void setNoQuarterState(State noQuarterState) {
        this.noQuarterState = noQuarterState;
    }

    public State getHasQuarterSate() {
        return hasQuarterSate;
    }

    public void setHasQuarterSate(State hasQuarterSate) {
        this.hasQuarterSate = hasQuarterSate;
    }

    public State getSoldState() {
        return soldState;
    }

    public void setSoldState(State soldState) {
        this.soldState = soldState;
    }
}

我们在添加一个游戏。

​ 我们已经实现了状态模式,所以实现这个游戏应该易如反掌。首先,我们在GumballMachine类中加入一个状态。

/**
 * 赢家状态
 */
public class WinnerState implements State {
    GumballMachine gumballMachine;
    /**
     * 我们通过构造器得到糖果机的引用,然后将它记录在实例变量中。
     */
    public WinnerState(GumballMachine gumballMachine){
        this.gumballMachine=gumballMachine;
    }

    @Override
    public void insertQuarter() {
        System.out.println("错误!");
    }

    @Override
    public void ejectQuarter() {
        System.out.println("错误!");
    }

    @Override
    public void tumCrank() {
        System.out.println("错误!");
    }

    @Override
    public void dispense() {
        System.out.println("你是赢家,用你的硬币可以得到两个糖果!");
        gumballMachine.releaseBall();
        if(gumballMachine.getCount() == 0){
            gumballMachine.setState(gumballMachine.getSoldOutSate());
        }else {
            // 如果还有第二颗糖果,我们就把它释放出来
            gumballMachine.releaseBall();
            if(gumballMachine.getCount()>0){
                gumballMachine.setState(gumballMachine.getNoQuarterState());
            }else {
                gumballMachine.setState(gumballMachine.getSoldOutSate());
            }
        }
    }
}

我们还要再做一个改变:我们需要实现机会随机数,还要增加一个进入WinnerState状态的装换。这两件事情都要加进HasQuarterState,因为顾客会从这个状态中转动曲柄:

代码修改:

/**
 * 已经投钱状态
 */
public class HasQuarterState implements State {
    Random random =new Random(System.currentTimeMillis());
    GumballMachine gumballMachine;
    /**
     * 我们通过构造器得到糖果机的引用,然后将它记录在实例变量中。
     */
    public HasQuarterState(GumballMachine gumballMachine){
        this.gumballMachine=gumballMachine;
    }

    /**
     * 这是一个对此状态不恰当的动作
     */
    @Override
    public void insertQuarter() {
        System.out.println("你不能再投入25分钱。");
    }

    /**
     * 退出顾客的25分钱,并将状态转换到NoQuarterState状态
     */
    @Override
    public void ejectQuarter() {
        System.out.println("退钱成功!");
        gumballMachine.setState(gumballMachine.getNoQuarterState());
    }

    /**
     * 当曲柄被转动时,我们就调用他的setState()方法,并传入soldState对象作为参数,将机器的状态转换到SoldState状态。
     * 这个SoldState对象可以通过getSoldState()方法取得(每个状态都有一个getter方法)
     */
    @Override
    public void tumCrank() {
        System.out.println("曲柄被转动。。。");
        int winner =random.nextInt(10);
        if((winner==0) && (gumballMachine.getCount()>1)){
            gumballMachine.setState(gumballMachine.getWinnerState());
        }else{
            gumballMachine.setState(gumballMachine.getSoldState());
        }
    }

    @Override
    public void dispense() {
        System.out.println("不能分发糖果!");
    }
}

复合模式

“模式的模式”

模式通常被一起使用,并被组合在同一个设计解决方案中,

复合模式在一个解决方案中结合两个或多个模式,以解决一般或重复发生的问题。

与鸭子重聚

鸭子将在同一个解决方案中展示模式是若何共存携手合作的。

1、首先,我们创建一个Quackable接口

我们将从头开始,而这一次,鸭子将实现Quackable接口。这样,我们就知道这个模拟器中,有那些东西可以呱呱叫,像是绿头鸭、红头鸭,甚至可以还会看到橡皮鸭偷偷溜回来。

public interface Quackable{
    // Quackable 只需要做一件事Quack(呱呱叫)
	public void quack();
}

2、现在、某些鸭子实现了Quackable接口

如果没有类实现某个接口,那么此接口的存在就没有意义。现在我们就来设计一些具体鸭子(不是那种“玩偶鸭”)

// 标准的绿头鸭
public class MallardDuck implements Quackable{
	public void quack(){
		System.out.println("Quack");
	}
}
public class RedheadDuck implements Quackavle{
    pubic void quack(){
        System.out.println("Quack");
    }
}

如果我们没有加入了别的种类的鸭子,就不太好玩了。

我们曾经加入了鸭鸣器(猎人使用的那种东西,它们肯定会呱呱叫)和橡皮鸭。

public class DuckCall implements Quackable {
    public void quack(){
        System.out.println("Kwak");
    }
}
public class RubberDuck implements Quackable{
    public void quack(){
        System.out.println("Squeak ");
    }
}

3、好了,我们有了鸭子,还需要一个模拟器
让我们来制造一个会产生一些鸭子,还需要确认鸭子会呱呱叫的模拟器。。。

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("\n Duck Simulator ");
        
        simulate(mallardDuck);
        simulate(redheadDuck);
        simulate(duckCall);
        simulate(rubberDuck);
        
    }
    void simulate(Quackable duck){
        duck.quack();
    }
}

4、当鸭子出现在这里时,鹅也应该在附近

只要有水塘的地方,就大概会有鸭子和鹅。我们为这个模拟器设计了一个Goose(鹅)类。

public class Goose {
    public void honk(){
        System.out.println("Honk");
    }
}

5、我们需要鹅适配器

我们模拟器期望看到Quackable接口。既然鹅不会呱呱叫,那么我们可以利用适配器将鹅适配成鸭子。

public class GooseAdapter implements Quackable {
    Goose goose;
    public GooseAdapter(Goose goose){
        this.goose=goose;
    }
    public void quack(){
        goose.honk();
    }
}

6、现在,模拟器也应该可以使用鹅了。

接着,我们需要做的就是创建Goose对象,将它包装进适配器,以便实现Quackable。这样,我们就可以继续了。

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();
        Quackable gooseDuck =new GooseAdapter(new Goose());
        System.out.println("\n Duck Simulator ");
        
        simulate(mallardDuck);
        simulate(redheadDuck);
        simulate(duckCall);
        simulate(rubberDuck);
        simulate(gooseDuck);
        
    }
    void simulate(Quackable duck){
        duck.quack();
    }
}

7、测试

这次测试时,simulate()会调用许多对象的quack()方法,其中包括适配器的quack()方法,结果应该会出现咯咯叫(honk)才对。

在这里插入图片描述

8、我们会让这些呱呱叫学家满意,让他们知道叫声的次数。

怎样才能办到呢?让我们创建一个装饰者,通过把鸭子包装进装饰者对象,给鸭子一些新行为(计算次数的行为)。我们不必修改鸭子的代码。

public class QuackCounter implements Quackable {
    Quackable duck;
    static  int numberOfQuacks;
    public QuackCounter(Quackable duck){
        this.duck=duck;
    }
    @Override
    public void quack() {
        duck.quack();
        numberOfQuacks++;
    }
    public  static int getQuacks(){
        return numberOfQuacks;
    }
}

9、我们需要更新此模拟器,以便创建被装饰的鸭子。

现在,我们必须包装在QuackCounter装饰者中被实例化的每个Quackable对象。如果不这么做,鸭子就会到处乱跑而使得我们无法统计器叫声次数

public class DuckSimulator{
    public static void main(String[] args){
        DuckSimulator simulator =new DuckSimulatoe();
        simulator.simulate();
    }
    void simulate(){
        Quackable mallardDuck =new QuackConter(new MallardDuck());
        Quackable redheadDuck =new QuackConter(new RedheadDuck());
        Quackable duckCall =new QuackConter(new DuckCall());
        Quackable rubberDuck =new QuackConter(new RubberDuck());
        Quackable gooseDuck =new GooseAdapter(new Goose());
        
        System.out.println(" \n Duck Sinmulator : With Decorator");
		simulate(mallardDuck);
        simulate(redheadDuck);
        simulate(duckCall);
        simulate(rubberDuck);
        simulate(gooseDuck);
        // 就是在这里我们为呱呱叫学家收集呱呱叫行为。
        System.out.println(" The ducks quacked " + QuackCounter.getQuacks()+ " times");
    }
    void simulate(Quackable duck){
        duck.quack();
    }
}

你必须装饰对象来获得被装饰过的行为。

包装的问题就是这样:有包装才有效果,没包装就没有效果。

为什么我们不将创建鸭子的程序集中在一个地方呢?换句话说,让我们将创建和装饰的部分包装起来吧。

这看起来像什么模式?

10、我们需要用工厂产生鸭子
我们需要一些质量控制确保鸭子一定是被包装起来的。我们要建造一个工厂,创建装饰过的鸭子。此工厂应该生产各种不同类型的鸭子的产品家族,所以我们要用抽象工厂模式。

// 我们定义一个抽象工厂,它的子类们会创建不同的家族。
public abstract class AbstractDuckFactory {
    public abstract Quackable createMallardDuck();
    public abstract Quackable createRedheadDuck();
    public abstract Quackable createDuckCall();
    public abstract Quackable createRubberDuck();
}

让我们从创建一个工厂开始,此工厂创建没有装饰者的鸭子:

public class DuckFactory extends AbstractDuckFactory {
    @Override
    public Quackable createMallardDuck() {
        return new MallardDuck();
    }
    @Override
    public Quackable createRedheadDuck() {
        return new RedheadDuck();
    }
    @Override
    public Quackable createDuckCall() {
        return new DuckCall();
    }
    @Override
    public Quackable createRubberDuck() {
        return new RubberDuck();
    }
}

现在,要创建我们真正需要的工厂,CountingDuckFactory:

public class CountingDuckFactory extends AbstractDuckFactory {
    @Override
    public Quackable createMallardDuck() {
        return new QuackCounter(new MallardDuck());
    }
    @Override
    public Quackable createRedheadDuck() {
        return new QuackCounter(new RedheadDuck());
    }
    @Override
    public Quackable createDuckCall() {
        return new QuackCounter(new DuckCall());
    }
    @Override
    public Quackable createRubberDuck() {
        return new QuackCounter(new RubberDuck());
    }
}

11、设置模拟器来使用这个工厂。

我们创建一个多态的方法,此方法需要一个用来创建对象的工厂。通过传入不同的工厂,我们就会得到不同的产品家族。

我们要修改一下simulate()方法,让它利用闯进来的工厂来创建鸭子。

public class DuckSimulator {
    // 我们的main()方法将让所有的事情动起来
    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("\nDuck Simulator:With Abstract Factory ");

		simulate(mallardDuck);
        simulate(redheadDuck);
        simulate(duckCall);
        simulate(rubberDuck);
        simulate(gooseDuck);
        // 就是在这里我们为呱呱叫学家收集呱呱叫行为。
        System.out.println(" The ducks quacked " + QuackCounter.getQuacks()+ " times");
    }
    void simulate(Quackable duck){
        duck.quack();
    }
}

要分别管理这些不同的鸭子变得有些困难,你能够帮我们作为一个整体来管理这些鸭子,甚至让我们管理几个想持续追踪的鸭子家族吗?

我们需要将鸭子视为一个集合,甚至是子集合,为了满足想管理鸭子家族的要求。如果我们下一次命令,就能让整个集合的鸭子听命行事,那就太好了。

12、让我们创建一群鸭子(实际上是一群Quackable)

组合模式允许我们像对待单个对象一样对待对象集合。

让我们逐步地看这是如何工作的:

// 组合需要和叶节点元素一样实现相同的接口,这里的“叶节点”就是Quackable
public class Flock implements Quackable {
    // 在每一个Flock内,我们使用ArrayList记录数据这个Flock 的Quackable对象
    ArrayList quackers =new ArrayList<>();
    // 用add()方法新增Quackabe 对象到Flock
    public void add (Quackable quackable){
        quackers.add(quackable);
    }
    // 毕竟Flock也是Quackable,所以也要具备quack()方法,此方法会对整群产生作用,我们遍历ArrayList调用每一个元素上的quack()。
    @Override
    public void quack() {
        Iterator iterator=quackers.iterator();
        while (iterator.hasNext()){
            Quackable quacker =(Quackable)iterator.next();
            quacker.quack();
            observable.notifyObserver();
        }
    }
}

13、现在我们需要修改模拟器

我们的组合已经准备好了,我们需要一些让鸭子能进入组合结构的代码。

public class DuckSimulator {
    // 我们的main()方法将让所有的事情动起来
    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("\nDuck Simulator ");

        Flock flockOfDucks =new Flock();
        flockOfDucks.add(mallardDuck);
        flockOfDucks.add(redheadDuck);
        flockOfDucks.add(duckcall);
        flockOfDucks.add(rubberDuck);

        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("\n  Duck Simulator: With Observer ");
        Quackologist quackologist=new Quackologist();
//        flockOfDucks.registerObservable(quackologist);
        flockOfMallards.registerObservable(quackologist);


        System.out.println("\n Duck Simulator: Whole Flock Simulation ");
        simulate(flockOfDucks);
        System.out.println("\n Duck Simulator: Mallard Flock Simulation ");
//        simulate(flockOfMallards);

        System.out.println("The ducks quacked "+ QuackCounter.getQuacks()+" times");
    }
    void simulate(Quackable duck){
        duck.quack();
    }
}

在组合模式中,组合(菜单)和叶节点(菜单项)具有一组相同的方法,其中包括了add()方法,就因为有一组相同的方法,我们才能在菜单项上调用不起作用的方法(像通过调用add()来在菜单项内加入一些东西)。这么设计的好处是,叶节点和合作之间是“透明的”。客户根本不用管究竟是组合还是叶节点,客户只是调用两者的同一个方法。

但是在这里,我们决定把组合维护孩子的方法和叶节点分开,也就是说,我们打算只让Flock具有add()方法,我们知道给一个Duck添加某些东西是无意义的。这样的设计比较“安全”,你不会调用无意义的方法,但是透明性比较差。现在,客户如果想调用add(),得先确定Quackable对象是Flock才行。

在OO设计的过程中,折中一直都是免不了的,在创建你自己的组合时,你需要考虑这些。

我们也需要追踪个别的鸭子。你能够有办法让我持续追踪个别鸭子的实时呱呱叫么?

似乎呱呱叫学家想要观察个别鸭子的行为,这让我们想起有一个模式可以观察对象的行为:观察者模式。

14、首先,我们需要一个Observable接口

所谓的Observable就是被观察的对象。Observable需要注册和通知观察者的方法。我们本来也需要删除观察者的方法,但是这里为了让实现保持简单,我们就省略这部分了。

// QuackObservable是一个接口,任何想被观察的Quack都必须实现QuackObservable接口
public interface QuackObservable {
    // 它具有注册观察者的方法,任何实现了Observer接口对象可以监听呱呱叫
    public void registerObservable(Observer observer);
    // 它也有观察者的方法
    public void notifyObserver();
}

现在我们需要确定所有的Quackable都实现此接口。。。

// 所以我们干脆让Oackable来扩展此接口。
public interface Quackable extends QuackObservable {
    public void quack();
}

15、现在我们必须确定所有实现Quackable 的具体类都能够扮演QuackObservabelde角色

我们需要在每一个类中实现注册和通知,但是这次我们要用稍微不一样的做法,我们要在另一个被称为Observable的类中封装注册和通知的代码,然后将它和QuackObservable组合在一起。这样,我们只需要一份代码即可,QuackObservable所有的调用都委托给Observabel辅助类。

先从Observable辅助类开始下手吧。

// Observable实现了所有必要的功能,我们只要把它给插进一个类,就可以让该类将工作委托给Observabel
public class Observable implements QuackObservable {
    ArrayList observers =new ArrayList();
    QuackObservable duck;
	// 在此构造器中,我们传进了QuackObservable,看到下面的notify()方法,你会发现当通知发生时,观察者把次对象传过去,好让观察者知道是那个对象在呱呱叫
    public Observable(QuackObservable duck){
        this.duck=duck;
    }
	// 这是注册挂插着的代码
    @Override
    public void registerObservable(Observer observer) {
        observers.add(observer);
    }
	// 这是通知用的代码
    @Override
    public void notifyObserver() {
        Iterator iterator=observers.iterator();
        while (iterator.hasNext()){
            Observer observer=(Observer) iterator.next();
            observer.update(duck);
        }
    }
}

16、整合Observable辅助类和Quackable类

这应该不算太糟,我们只是要确认Quackable 类是和Observabel组合在一起的,并且他们知道怎样来委托工作。然后,它们就准备好成为Observable了。

下面是MallardDuck的实现,其他的鸭子实现也类似。

public class MallardDuck implements Quackable {
    // 每个Quackable都有一个Observable实例变量。
    Observable observable;
	// 在构造器中,我们创建一个Observable,并传入一个MallardDuck对象的引用
    public MallardDuck(){
        observable=new Observable(this);
    }
    // 当我们呱呱叫,需要让观察者知道
    @Override
    public void quack() {
        System.out.println("Quack!");
        notifyObserver();
    }
	// 这是我们的两个QuackObservable方法,注意我们只是委托给辅助类进行
    @Override
    public void registerObservable(Observer observer) {
        observable.registerObservable(observer);
    }

    @Override
    public void notifyObserver() {
        observable.notifyObserver();
    }
}

17、几乎已经大功告成,我们还需要把模式的Observer端完成。

我们已经实现了Observable所需要的一切,现在我们需要一些观察者,我们先从Observer接口开始:

// Observer接口只有一个方法,就是update(),他需要传入正在呱呱叫的对象。
public interface Observer {
    public void update(QuackObservable duck);
}

现在我们需要一个观察者:

// 我们需要实现Observable接口,否则就无法以QuackObservable注册
public class Quackologist implements Observer {
    // Quackologist很简单,只有一个方法,update()。它打印出
    @Override
    public void update(QuackObservable duck) {
        System.out.println("Quackologist: "+ duck + " just quacked.");
    }
}

18、我们准备开始观察了。让我们更新模拟器,试试看:

public class DuckSimulator {
    // 我们的main()方法将让所有的事情动起来
    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("\nDuck Simulator ");

        Flock flockOfDucks =new Flock();
        flockOfDucks.add(mallardDuck);
        flockOfDucks.add(redheadDuck);
        flockOfDucks.add(duckcall);
        flockOfDucks.add(rubberDuck);

        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("\n  Duck Simulator: With Observer ");
        // 我们在这里所需要做的事,就是穿件一个Quackologist把它注册成为一个群的观察者
        Quackologist quackologist=new Quackologist();
        flockOfDucks.registerObservable(quackologist);
        flockOfMallards.registerObservable(quackologist);


        System.out.println("\n Duck Simulator: Whole Flock Simulation ");
        simulate(flockOfDucks);
        System.out.println("\n Duck Simulator: Mallard Flock Simulation ");
        simulate(flockOfMallards);

        System.out.println("The ducks quacked "+ QuackCounter.getQuacks()+" times");
    }
    void simulate(Quackable duck){
        duck.quack();
    }
}

我们做了什么

我们从一大堆Quackable开始。。。

有一个鹅出现了,他希望自己像一个Quackable。

所以我们利用适配器模式,将鹅适配成Quackable,现在你就可以调用鹅适配器的quack()方法来让鹅咯咯叫。

然后,呱呱叫学家决定要计算呱呱叫声的次数。

所以我们使用装饰者模式,添加了一个名为QuackCounter的装饰着。它用来追踪quack()被调用的次数,并将调用委托给所装饰的quackable对象。

但是呱呱叫学家担心他们忘了加上QuackCounter装饰着。

所以我们使用抽象工厂模式创建鸭子。从此以后,当他们需要鸭子时,就直接跟工厂要,工厂会给他们装饰过的鸭子。(如果他们想取得没装饰的鸭子,用另一个鸭子工厂就可以!)

有是鸭子,有是鹅,有事quackable的。我们有管理上的困扰。

所以我们需要使用组合模式,将许多quackable集结成一个群。这个模式也允许群中有群,以便让呱呱叫家来管理鸭子家族。我们在实现中通过使用ArrayList中的java.util的迭代器而使用了迭代器模式。

当任何呱呱声响起时,呱呱叫学家都希望能被告知。

所以我们使用观察者模式,让呱呱叫学家注册成为观察者。现在,当呱呱声响起时,呱呱叫学家就会被通知了。在这个实现中,我们再度用到了迭代器。呱呱叫学家不仅可以当某个鸭子观察者,甚至可以当一整群的观察者。

模式

是在某种境(context)下,针对某问题的某种解决方案。

这个定义并不会让人有恍然大悟的感觉,现在就逐步了解定义中所提到的情境、问题、解决方案:

情境:就是应用某个模式的情况。这应该是会不断出现的情况。

问题:就是你想在某种情境下达到的目标,但也可以是某情境下的约束。

解决方案就是你所追求的:一个通用的设计,用来解决约束、达到目标。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值