讨伐设计模式——创建型模式篇

本文是学习设计模式时做的笔记,原视频链接:

https://www.bilibili.com/video/BV1G4411c7N4

目录

设计模式分类

单例模式(Singleton)

使用场景

实现方式

饿汉式(静态常量)

饿汉式(静态代码块)

懒汉式(线程不安全)

懒汉式(线程安全,同步方法)

懒汉式(线程安全,同步代码块)

双重检查

静态内部类

枚举

工厂模式(Factory)

使用场景

实现方式

需求:披萨订购

注意事项

抽象工厂模式(Abstract Factory)

使用场景

实现方式

原型模式(Prototype)

使用场景

实现方式

需求:克隆羊

使用原型模式

原型模式的深拷贝问题

浅拷贝

深拷贝

原型模式实现深拷贝

建造者模式

使用场景

实现方式

需求:快餐店套餐


设计模式分类

设计模式分为三种类型,共23种。

创建型模式:单例模式、工厂模式、抽象工厂模式、原型模式、建造者模式。

结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。

行为型模式:模板方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、责任链模式。

单例模式(Singleton)

单例模式:采取一定的方法保证在整个软件系统中,某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(这个方法通常是静态方法)。

使用场景

  • 需要频繁地进行创建和销毁的对象。
  • 创建对象耗时过多或耗费资源过多,但又经常用到的对象。

实现方式

单例模式有八种实现方式:

  • 饿汉式(静态常量)
  • 饿汉式(静态代码块)
  • 懒汉式(线程不安全)
  • 懒汉式(线程安全,同步方法)
  • 懒汉式(线程安全,同步代码块)
  • 双重检查
  • 静态内部类
  • 枚举

【剧透一下】

推荐使用双重检查、静态内部类、枚举的方式;

可以使用两种饿汉式方式;

不建议使用三种懒汉式方式。

饿汉式(静态常量)

实现步骤:

  1. 构造器私有化
  2. 在类的内部创建对象实例
  3. 向外暴露一个静态的公共方法

代码演示:

class Singleton {
	// 1.构造器私有化
	private Singleton() {
	}

	// 2.在本类内部创建对象实例
	private final static Singleton instance = new Singleton();

	// 3. 对外提供一个公有的静态方法,返回实例对象
	public static Singleton getInstance() {
		return instance;
	}
}

测试:

public class Test {
	public static void main(String[] args) {
		Singleton instance = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		System.out.println(instance.hashCode());
		System.out.println(instance2.hashCode());
	}
}

经测试,instance与instance2的hashCode相同,说明是同一对象。

分析:

  • 优点:这种写法比较简单,在类装载的时候就完成实例化,避免了线程同步问题。
  • 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果,可能会造成内存的浪费。

饿汉式(静态代码块)

实现步骤:

  • 与上个方法不同的是,在类的内部创建类的实例后,将创建单例对象的步骤放到静态代码块中执行。

代码演示:

class Singleton {
	// 1.构造器私有化
	private Singleton() {
	}

	// 2.在本类内部创建类的实例,在静态代码块中创建单例对象
	private static Singleton instance;

	static {
		instance = new Singleton();
	}

	// 3. 对外提供一个公有的静态方法,返回实例对象
	public static Singleton getInstance() {
		return instance;
	}
}

分析:

  • 这种方式也是在类装载的时候就完成实例化,所以优缺点和上个方法是一样的。

懒汉式(线程不安全)

实现步骤:

  1. 构造器私有化
  2. 在类的内部创建类的实例,但不创建单例对象
  3. 向外暴露一个静态的公共方法,当使用到该方法时,才去创建单例对象

代码演示:

class Singleton {
	// 1.构造器私有化
	private Singleton() {
	}

	// 2.在本类内部创建类的实例
	private static Singleton instance;

	// 3.对外提供一个公有的静态方法,当使用到该方法时,才去创建单例对象
	public static Singleton getInstance() {
		if (instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
}

分析:

  • 优点:起到了Lazy Loading的效果,但只能在单线程下使用。
  • 缺点:在多线程下,一个线程执行到 if(instance == null) 判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以这种实现方式是线程不安全的。

懒汉式(线程安全,同步方法)

实现步骤:

  • 在上个方法的基础上,给对外提供的公有方法上同步锁,解决线程不安全问题。

代码演示:

class Singleton {
	// 1.构造器私有化
	private Singleton() {
	}

	// 2.在本类内部创建类的实例
	private static Singleton instance;

	// 3.对外提供一个公有的静态方法,当使用到该方法时,才去创建单例对象
    // 4.给这个公有方法上同步锁
	public static synchronized Singleton getInstance() {
		if (instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
}

分析:

  • 优点:解决了线程不安全问题。
  • 缺点:每个线程在想获得类的实例的时候,都要进行线程同步,导致效率很低。

懒汉式(线程安全,同步代码块)

实现步骤:

  • 为了解决上个方法效率低的问题, 不再对公有方法上锁,而是对创建单例对象的语句上锁。

代码演示:

class Singleton {
	// 1.构造器私有化
	private Singleton() {
	}

	// 2.在本类内部创建类的实例
	private static Singleton instance;

	// 3.对外提供一个公有的静态方法,当使用到该方法时,才去创建单例对象
	public static synchronized Singleton getInstance() {
		if (instance == null) {
            // 4.给创建单例对象的语句上同步锁
            synchronized (Singleton.class) {
			    instance = new Singleton();
            }
		}
		return instance;
	}
}

分析:

  • 优点:无。
  • 缺点:本意是想对上个方法进行改进,实际上不能起到线程同步的作用,所以这种方式是线程不安全的。

双重检查

实现步骤:

  1. 构造器私有化
  2. 在类的内部创建类的实例(需要加volatile关键字)
  3. 向外暴露一个静态的公共方法
  4. 在公共方法中对是否已存在单例对象进行双重检查

代码演示:

class Singleton {
	// 1.构造器私有化
	private Singleton() {
	}

	// 2.在本类内部创建类的实例,这里要加一个volatile关键字
	private static volatile Singleton instance;

	// 3.对外提供一个公有的静态方法,当使用到该方法时,才去创建单例对象
	public static Singleton getInstance() {
		// 4.对是否已存在单例对象进行双重检查
		if (instance == null) {
			synchronized (Singleton.class) {
				if (instance == null) {
					instance = new Singleton();
				}
			}
		}
		return instance;
	}
}

volatile关键字在这里的作用是防止指令重排序。

执行语句  T t = new T()  创建一个对象T:

汇编码:

  1. new #2 <T>                          :申请一块内存,给成员变量赋默认值。(半初始化)
  2. dup                                       :不重要。
  3. invokespecial #3 <T.<init>>  :执行构造方法。
  4. astore                                   :将对象名t和对象建立关联。
  5. return                                    :返回。

为了防止invokespecial和astore两条指令发生重排序,双重检查锁必须要加volatile关键字。

这里看不懂也没关系,只要记住加volatile关键字就好了。

分析:

  • 优点:解决了懒加载问题,同时解决了线程安全问题,同时保证了效率。推荐使用。
  • 缺点:无。

静态内部类

实现步骤:

  1. 构造器私有化
  2. 在类的内部创建静态内部类,在静态内部类中创建单例对象
  3. 向外暴露一个静态的公共方法,当调用该方法时,静态内部类被装载(只装载一次),获得单例对象

代码演示:

class Singleton {
	// 1.构造器私有化
	private Singleton() {
	}

	// 2.在本类内部创建静态内部类,在静态内部类中创建单例对象
	private static class SingletonInstance {
		private static final Singleton INSTANCE = new Singleton();
	}

	// 3.对外提供一个公有的静态方法,当使用到该方法时,静态内部类被装载,获得单例对象
	public static synchronized Singleton getInstance() {
		return SingletonInstance.INSTANCE;
	}
}

当Singleton类装载的时候,静态内部类SingletonInstanc不被装载。

分析:

  • 优点:解决了懒加载问题,同时解决了线程安全问题,同时保证了效率。推荐使用。
  • 缺点:无。

枚举

实现步骤:

  • 使用枚举

代码演示:

enum Singleton {
    INSTANCE;
    public void sayOK () {
        System.out.println("ok~");
    }
}

测试1:

public class Test {
	public static void main(String[] args) {
		Singleton instance = Singleton.INSTANCE;
		Singleton instance2 = Singleton.INSTANCE;
		System.out.println(instance.hashCode());
		System.out.println(instance2.hashCode());
	}
}

经测试,instance与instance2的hashCode相同,说明是同一对象。

测试2:

public class Test {
	public static void main(String[] args) {
		Singleton instance = Singleton.INSTANCE;
		instance.sayOK();
	}
}

输出结果:

ok~

分析:

  • 优点:借助枚举来实现单例模式,避免了多线程同步问题,还能防止反序列化重新创建新的对象。推荐使用。
  • 缺点:无。

工厂模式(Factory)

工厂模式:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类。工厂模式使其创建过程延迟到子类进行。

使用场景

不同条件下创建不同的实例。

实现方式

需求:披萨订购

请完成某披萨店(Pizzeria)的披萨订购系统。

  1. 披萨种类很多,比如 黄油披萨(ButterPizza)、芝士披萨(CheesePizza)、胡椒披萨(PepperPizza)等。
  2. 披萨种类容易扩展。

实现步骤:

1.创建接口(Pizza)

public interface Pizza {
    void produce();
}

2.创建实现接口的实体类(ButterPizza、CheesePizza、PepperPizza)

public class ButterPizza implements Pizza {
    @Override
    public void produce() {
        System.out.println("生成黄油披萨");
    }
}
public class CheesePizza implements Pizza {
    @Override
    public void produce() {
        System.out.println("生成芝士披萨");
    }
}
public class PepperPizza implements Pizza {
    @Override
    public void produce() {
        System.out.println("生成胡椒披萨");
    }
}

3.创建一个工厂(Pizzeria),生成实体类对象

public class Pizzeria {
    public Pizza getPizza(String pizzaType) {
        if (pizzaType == null) {
            return null;
        }
        if (pizzaType.equalsIgnoreCase("ButterPizza")) {
            return new ButterPizza();
        } else if (pizzaType.equalsIgnoreCase("CheesePizza")) {
            return new CheesePizza();
        } else if (pizzaType.equalsIgnoreCase("PepperPizza")) {
            return new PepperPizza();
        }
        return null;
    }
}

4.使用该工厂(Pizzeria),通过传递类型信息来获取实体类对象

public class Test {
    public static void main(String[] args) {
        Pizzeria pizzeria = new Pizzeria();
        Pizza pizza = null;
        pizza = pizzaFactory.getPizza("butterpizza");
        pizza.produce();
        pizza = pizzaFactory.getPizza("cheesepizza");
        pizza.produce();
        pizza = pizzaFactory.getPizza("pepperpizza");
        pizza.produce();
    }
}

输出结果:

生成黄油披萨
生成芝士披萨
生成胡椒披萨

在上面测试类(Test)中将生成披萨的代码写死了,如果要对其进行改进:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Test {
    public static void main(String[] args) {
        PizzaFactory pizzaFactory = new PizzaFactory();
        Pizza pizza;
        String pizzaType;
        Test t = new Test();
        do {
            pizzaType = t.getType();
            pizza = pizzaFactory.getPizza(pizzaType);
            if (pizza != null) {
                pizza.produce();
            } else {
                System.out.println("生成披萨失败");
            }
        } while (true);
    }
    private String getType() {
        try {
            BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("Input pizza type:");
            String str = strin.readLine();
            return str;
        } catch (IOException e) {
            e.printStackTrace();
            return "";
        }
    }
}

注意事项

作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式,而需要生成简单对象,特别是只需要通过new就可以完成创建的对象时,无需使用工厂模式。如果使用工厂模式,需要引入一个工厂类,会增加系统的复杂度。

抽象工厂模式(Abstract Factory)

抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

围绕一个超级工厂创建其它工厂,该超级工厂又称为其它工厂的工厂,每个生成的工厂都能按照工厂模式提供对象。

使用场景

系统的产品又多于一个的产品族,而系统只消费其中某一族的产品。

实现方式

在工厂模式的基础上,多了一层抽象工厂。

我们仍然以披萨订购为例,之前的披萨店售出黄油披萨(ButterPizza)、芝士披萨(CheesePizza)、胡椒披萨(PepperPizza)等种类的披萨,后来生意兴隆,在北京(Beijing)和上海(Shanghai)开了分店。

实现步骤:

1.为披萨种类创建一个接口(Pizza)

public interface Pizza {
    void produce();
}

2.创建实现披萨种类接口的实体类(ButterPizza、CheesePizza、PepperPizza)

public class ButterPizza implements Pizza {
    @Override
    public void produce() {
        System.out.println("生成黄油披萨");
    }
}
public class CheesePizza implements Pizza {
    @Override
    public void produce() {
        System.out.println("生成芝士披萨");
    }
}
public class PepperPizza implements Pizza {
    @Override
    public void produce() {
        System.out.println("生成胡椒披萨");
    }
}

3.为城市创建一个接口(City)

public interface City {
    void sold();
}

4.创建实现城市接口的实体类(Beijing、Shanghai)

public class Beijing implements City {
    @Override
    public void sold() {
        System.out.println("在北京出售");
    }
}
public class Shanghai implements City {
    @Override
    public void sold() {
        System.out.println("在上海出售");
    }
}

5.创建抽象类(AbstractFactory)来获取工厂

public abstract class AbstractFactory {
    public abstract Pizza getPizza(String pizzaType);
    public abstract City getCity(String city);
}

6.创建披萨种类工厂(PizzaFactory)和城市工厂(CityFactory)

public class PizzaFactory extends AbstractFactory {
    @Override
    public Pizza getPizza(String pizzaType) {
        if (pizzaType == null) {
            return null;
        }
        if (pizzaType.equalsIgnoreCase("ButterPizza")) {
            return new ButterPizza();
        } else if (pizzaType.equalsIgnoreCase("CheesePizza")) {
            return new CheesePizza();
        } else if (pizzaType.equalsIgnoreCase("PepperPizza")) {
            return new PepperPizza();
        }
        return null;
    }

    @Override
    public City getCity(String city) {
        return null;
    }
}
public class CityFactory extends AbstractFactory {
    @Override
    public Pizza getPizza(String pizzaType) {
        return null;
    }

    @Override
    public City getCity(String city) {
        if (city == null) {
            return null;
        }
        if (city.equalsIgnoreCase("Beijing")) {
            return new Beijing();
        } else if (city.equalsIgnoreCase("Shanghai")) {
            return new Shanghai();
        }
        return null;
    }
}

7.创建一个工厂生成器(FactoryProducer)来获取工厂

public class FactoryProducer {
    public static AbstractFactory getFactory(String choice) {
        if (choice.equalsIgnoreCase("Pizza")) {
            return new PizzaFactory();
        } else if (choice.equalsIgnoreCase("City")) {
            return new CityFactory();
        }
        return null;
    }
}

8.使用工厂生成器来获取抽象工厂,通过传递类型信息来获取实体类对象

public class Test {
    public static void main(String[] args) {
        AbstractFactory pizzaFactory = FactoryProducer.getFactory("Pizza");
        AbstractFactory cityFactory = FactoryProducer.getFactory("City");

        Pizza pizza1 = pizzaFactory.getPizza("ButterPizza");
        pizza1.produce();
        Pizza pizza2 = pizzaFactory.getPizza("CheesePizza");
        pizza2.produce();
        City city1 = cityFactory.getCity("Beijing");
        city1.sold();

        Pizza pizza3 = pizzaFactory.getPizza("PepperPizza");
        pizza3.produce();
        City city2 = cityFactory.getCity("Shanghai");
        city2.sold();
    }
}

输出结果:

生成黄油披萨

生成芝士披萨

在北京出售

生成胡椒披萨

在上海出售

原型模式(Prototype)

原型模式:用原型实例指定创建对象的种类,并且通过拷贝原型实例创建新的对象。

原型模式提供了一种创建对象的最佳方式,用于创建重复的对象,同时又能保证性能。

使用场景

直接创建对象的代价比较大时,可以通过拷贝已有的原型实例创建新的对象。

实现方式

工作原理:创建对象=原型对象.clone()。

需求:克隆羊

现在有一只名为Dolly的羊,性别为雌性(female),颜色为白色(white)。

请编写程序,创建和Dolly属性完全相同的10只羊。

1.首先创建Dolly。

public class Sheep {
    private String name;
    private String gender;
    private String color;

    public Sheep(String name, String gender, String color) {
        super();
        this.name = name;
        this.gender = gender;
        this.color = color;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getGender() {
        return gender;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public String getColor() {
        return color;
    }

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", color='" + color + '\'' +
                '}';
    }
}
public class Test {
    public static void main(String[] args) {
        Sheep sheep = new Sheep("Dolly", "female", "white");
}

2.接下来我们对Dolly进行拷贝。

若采用传统方式:

public class Test {
    public static void main(String[] args) {
        Sheep sheep = new Sheep("Dolly", "female", "white");
        
        Sheep sheep1 = new Sheep(sheep.getName(), sheep.getGender(), sheep.getColor());
        Sheep sheep2 = new Sheep(sheep.getName(), sheep.getGender(), sheep.getColor());
        Sheep sheep3 = new Sheep(sheep.getName(), sheep.getGender(), sheep.getColor());
        Sheep sheep4 = new Sheep(sheep.getName(), sheep.getGender(), sheep.getColor());
        Sheep sheep5 = new Sheep(sheep.getName(), sheep.getGender(), sheep.getColor());
        Sheep sheep6 = new Sheep(sheep.getName(), sheep.getGender(), sheep.getColor());
        Sheep sheep7 = new Sheep(sheep.getName(), sheep.getGender(), sheep.getColor());
        Sheep sheep8 = new Sheep(sheep.getName(), sheep.getGender(), sheep.getColor());
        Sheep sheep9 = new Sheep(sheep.getName(), sheep.getGender(), sheep.getColor());
        Sheep sheep10 = new Sheep(sheep.getName(), sheep.getGender(), sheep.getColor());
    }
}

这么写很容易理解,而且简单、易操作。

但这样做的缺点是:

  • 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象很复杂时,效率较低。
  • 总是需要重新初始化对象,而不是动态地获得对象运行时的状态,不够灵活。

在Java中,Object类是所有类的父类。Object类提供了一个clone()方法,调用该方法可以对某一个Java对象进行克隆。

对Java对象进行克隆的前提是,Java对象对应的Java类必须实现接口Cloneable,顾名思义,可克隆的,实现了Cloneable接口的Java类的对象具有克隆的能力。

首先Sheep类实现Cloneable接口(并重写clone()方法):

public class Sheep implements Cloneable {
    private String name;
    private String gender;
    private String color;

    public Sheep(String name, String gender, String color) {
        super();
        this.name = name;
        this.gender = gender;
        this.color = color;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getGender() {
        return gender;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public String getColor() {
        return color;
    }

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", color='" + color + '\'' +
                '}';
    }
    
    public Object clone() {
        Object clone = null;
        try {
            clone = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return clone;
    }
}

然后对Dolly进行拷贝:

public class Test {
    public static void main(String[] args) {
        Sheep sheep = new Sheep("Dolly", "female", "white");
        
        Sheep sheep1 = (Sheep) sheep.clone();
        Sheep sheep2 = (Sheep) sheep.clone();
        Sheep sheep3 = (Sheep) sheep.clone();
        Sheep sheep4 = (Sheep) sheep.clone();
        Sheep sheep5 = (Sheep) sheep.clone();
        Sheep sheep6 = (Sheep) sheep.clone();
        Sheep sheep7 = (Sheep) sheep.clone();
        Sheep sheep8 = (Sheep) sheep.clone();
        Sheep sheep9 = (Sheep) sheep.clone();
        Sheep sheep10 = (Sheep) sheep.clone();
    }
}

使用原型模式

1.创建一个实现了Cloneable接口的抽象类(Sheep)

public abstract class Sheep implements Cloneable {
    private String name;
    private String gender;
    private String color;

    abstract void action();//为了看起来很了不起,这里加了一个抽象方法

    public Sheep(String name, String gender, String color) {
        this.name = name;
        this.gender = gender;
        this.color = color;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getGender() {
        return gender;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public String getColor() {
        return color;
    }

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", color='" + color + '\'' +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

2.创建扩展了上面抽象类的实体类(Dolly、Tom)

public class Dolly extends Sheep {
    public Dolly() {
        super("Dolly", "female", "white");
        action();
    }

    @Override
    void action() {
        System.out.println("Dolly出生");
    }
}
public class Tom extends Sheep {
    public Tom() {
        super("Tom", "male", "black");
        action();
    }

    @Override
    void action() {
        System.out.println("Tom出生");
    }
}

3.将实体类对象作为原型进行克隆

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        Sheep sheep1 = new Dolly();
        System.out.println(sheep1);
        Sheep sheep2 = new Tom();
        System.out.println(sheep2);
        Sheep sheep3 = (Sheep) sheep1.clone();
        System.out.println(sheep3);
        Sheep sheep4 = (Sheep) sheep2.clone();
        System.out.println(sheep4);
    }
}

输出结果:

Dolly出生
Sheep{name='Dolly', gender='female', color='white'}
Tom出生
Sheep{name='Tom', gender='male', color='black'}
Sheep{name='Dolly', gender='female', color='white'}
Sheep{name='Tom', gender='male', color='black'}

原型模式的深拷贝问题

浅拷贝

Object类中的clone()方法默认使用浅拷贝。

对于引用数据类型的成员变量(比如该成员变量是某个数组或某个类的对象),浅拷贝会进行引用传递,也就是说只是将该成员变量的引用值(内存地址)进行复制。

实际上两个对象的该成员变量指向同一个实例,在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。

深拷贝

深拷贝会为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象。

原型模式实现深拷贝

  • 方式1:重写clone()方法来实现深拷贝
  • 方式2:通过对象序列化实现深拷贝

建造者模式(Builder)

建造模式:将一个复杂的构建过程与其表示相分离,使得同样的构建过程可以创建不同的表示。

使用场景

去肯德基,汉堡、可乐、薯条、炸鸡是不会变的,而它们的组合方式经常变化。

实现方式

建造者模式原理类图:

其中,

Product(产品):一个具体的产品对象。

Builder(抽象建造者):一个接口/抽象类,提供构建和装配Product各个部件的抽象方法。

ConcreteBuilder(具体建造者):Builder的实现类。

Director(指挥者):构建一个使用Builder接口的对象,用于创建Product。Director的作用是隔离客户与对象的生产过程。

需求:快餐店套餐

某快餐店出售汉堡(Burger)和饮料(Grink)。

汉堡包括:

  • 蔬菜堡(VegBurger),5.0元
  • 鸡肉堡(ChickenBurger),8.0元

饮料包括:

  • 牛奶(Milk):3.5元
  • 可乐(Cola):4元

该快餐店现在要推出两种套餐:

  • 套餐A(SetMealA):蔬菜堡+牛奶
  • 套餐B(SetMealB):蔬菜堡+鸡肉堡+可乐

请编写程序,实现该快餐店业务需求。

回顾建造者模式

1.我们先来设计产品Product

在快餐店当前的需求中,产品是什么?

不是汉堡Burger,也不是饮料Grink,而是套餐SetMeal。

SetMeal的组成要素是什么?

SetMeal是若干汉堡和饮料的组合,我们用一个ArrayList集合来表示一个SetMeal对象。

在SetMeal中既可以有汉堡也可以有饮料,我们把汉堡和饮料统一定义为单品(Item)。

首先创建表示单品Item的接口(每种单品的属性包括名称name和价格price):

public interface Item {
    public String name();
    public float price();
}

分别创建汉堡(抽象类Buiger)和饮料(抽象类Grink)实现Item接口:

public abstract class Burger implements Item {
}
public abstract class Grink implements Item {
}

然后创建实体类蔬菜堡(VegBurger)、鸡肉堡(ChickenBurger)、牛奶(Milk)、可乐(Cola):

public class VegBurger extends Burger {
    @Override
    public String name() {
        return "VegBurger";
    }
    @Override
    public float price() {
        return 5.0f;
    }
}
public class ChickenBurger extends Burger {
    @Override
    public String name() {
        return "ChickenBurger";
    }
    @Override
    public float price() {
        return 8.0f;
    }
}
public class Milk extends Grink {
    @Override
    public String name() {
        return "Milk";
    }
    @Override
    public float price() {
        return 3.5f;
    }
}
public class Cola extends Grink {
    @Override
    public String name() {
        return "Cola";
    }
    @Override
    public float price() {
        return 4.0f;
    }
}

准备工作完成后,创建实体类套餐(SetMeal):

import java.util.ArrayList;
import java.util.List;

public class SetMeal {
    private List<Item> items = new ArrayList<Item>();
    //向套餐中添加单品
    public void addItem(Item item) {
        items.add(item);
    }
    //计算套餐总价格
    public float getCost() {
        float cost = 0.0f;
        for (Item item : items) {
            cost += item.price();
        }
        return cost;
    }
    //打印套餐信息
    public void showSetMeal() {
        for (Item item : items) {
            System.out.print("Item: " + item.name());
            System.out.println(", Price: " + item.price());
        }
    }
}

2.然后我们考虑抽象建造者Builder

Builder的作用是什么?Builder是一个接口/抽象类,提供构建和装配Product各个部件的抽象方法。

Product是套餐SetMeal,那么Product的部件就是各种单品Item。

我们以蔬菜堡为例,构建蔬菜堡的操作为:

new VegBurger(); //很好理解

装配蔬菜堡就是将VegBurger对象放进套餐的ArrayList集合,通过调用SetMeal类中提供的add方法实现。

所以不难写出抽象建造者SetMealBuilder:

public abstract class SetMealBuilder {
    public abstract SetMeal getSetMeal();
}

具体建造者SetMealA、SetMealB:

public class SetMealA extends SetMealBuilder {
    @Override
    public SetMeal getSetMeal() {
        SetMeal setMeal = new SetMeal();
        setMeal.addItem(new VegBurger());
        setMeal.addItem(new Milk());
        return setMeal;
    }
}
public class SetMealB extends SetMealBuilder {
    @Override
    public SetMeal getSetMeal() {
        SetMeal setMeal = new SetMeal();
        setMeal.addItem(new VegBurger());
        setMeal.addItem(new ChickenBurger());
        setMeal.addItem(new Cola());
        return setMeal;
    }
}

3.接下来我们来考虑指挥者Director

怎么理解Director?

Director的作用是隔离客户Client与对象的生产过程。

直白地讲,Director要从程序中获取产品对象交给Client。

public class SetMealDirector {
    SetMealBuilder setMealBuilder = null;

    public SetMealDirector(SetMealBuilder setMealBuilder) {
        this.setMealBuilder = setMealBuilder;
    }

    public SetMeal buildSetMeal() {
        SetMeal setMeal = setMealBuilder.getSetMeal();//获取产品对象
        return setMeal;
    }
}

如果感觉这里不太好理解,我们从Client的角度来分析:

public class Client {
    public static void main(String[] args) {
        //Client表示想获得一个套餐A
        SetMealA setMealA = new SetMealA();
        //Client找到一个会做套餐A的director
        SetMealDirector director = new SetMealDirector(setMealA);
        //director带着自己的团队做了一个套餐A
        SetMeal setMeal = director.buildSetMeal();
        //Client看了看套餐A
        setMeal.showSetMeal();
        //Client看了看价格
        System.out.println("Cost: " + setMeal.getCost() + "元");
    }
}

输出结果:

Item: VegBurger, Price: 5.0
Item: Milk, Price: 3.5
Cost: 8.5元

加油!

猜你喜欢

转载自blog.csdn.net/qq_42082161/article/details/111643125