本文是学习设计模式时做的笔记,原视频链接:
https://www.bilibili.com/video/BV1G4411c7N4
目录
设计模式分类
设计模式分为三种类型,共23种。
创建型模式:单例模式、工厂模式、抽象工厂模式、原型模式、建造者模式。
结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
行为型模式:模板方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、责任链模式。
单例模式(Singleton)
单例模式:采取一定的方法保证在整个软件系统中,某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(这个方法通常是静态方法)。
使用场景
- 需要频繁地进行创建和销毁的对象。
- 创建对象耗时过多或耗费资源过多,但又经常用到的对象。
实现方式
单例模式有八种实现方式:
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法)
- 懒汉式(线程安全,同步代码块)
- 双重检查
- 静态内部类
- 枚举
【剧透一下】
推荐使用双重检查、静态内部类、枚举的方式;
可以使用两种饿汉式方式;
不建议使用三种懒汉式方式。
饿汉式(静态常量)
实现步骤:
- 构造器私有化
- 在类的内部创建对象实例
- 向外暴露一个静态的公共方法
代码演示:
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;
}
}
分析:
- 这种方式也是在类装载的时候就完成实例化,所以优缺点和上个方法是一样的。
懒汉式(线程不安全)
实现步骤:
- 构造器私有化
- 在类的内部创建类的实例,但不创建单例对象
- 向外暴露一个静态的公共方法,当使用到该方法时,才去创建单例对象
代码演示:
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;
}
}
分析:
- 优点:无。
- 缺点:本意是想对上个方法进行改进,实际上不能起到线程同步的作用,所以这种方式是线程不安全的。
双重检查
实现步骤:
- 构造器私有化
- 在类的内部创建类的实例(需要加volatile关键字)
- 向外暴露一个静态的公共方法
- 在公共方法中对是否已存在单例对象进行双重检查
代码演示:
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:
汇编码:
- new #2 <T> :申请一块内存,给成员变量赋默认值。(半初始化)
- dup :不重要。
- invokespecial #3 <T.<init>> :执行构造方法。
- astore :将对象名t和对象建立关联。
- return :返回。
为了防止invokespecial和astore两条指令发生重排序,双重检查锁必须要加volatile关键字。
这里看不懂也没关系,只要记住加volatile关键字就好了。
分析:
- 优点:解决了懒加载问题,同时解决了线程安全问题,同时保证了效率。推荐使用。
- 缺点:无。
静态内部类
实现步骤:
- 构造器私有化
- 在类的内部创建静态内部类,在静态内部类中创建单例对象
- 向外暴露一个静态的公共方法,当调用该方法时,静态内部类被装载(只装载一次),获得单例对象
代码演示:
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)的披萨订购系统。
- 披萨种类很多,比如 黄油披萨(ButterPizza)、芝士披萨(CheesePizza)、胡椒披萨(PepperPizza)等。
- 披萨种类容易扩展。
实现步骤:
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元
加油!