目录
[1. 摘要](#1. 摘要)
[2. 需求案例(设计一个咖啡店的点餐系统)](#2. 需求案例(设计一个咖啡店的点餐系统))
[2.1 咖啡父类及其子类](#2.1 咖啡父类及其子类)
[2.2 咖啡店类与咖啡类的关系](#2.2 咖啡店类与咖啡类的关系)
[3. 普通方法实线咖啡店点餐系统](#3. 普通方法实线咖啡店点餐系统)
[3.1 定义Coffee父类](#3.1 定义Coffee父类)
[3.2 定义美式咖啡类继承Coffee类](#3.2 定义美式咖啡类继承Coffee类)
[3.3 定义拿铁咖啡继承Coffee类](#3.3 定义拿铁咖啡继承Coffee类)
[3.4 定义咖啡店类](#3.4 定义咖啡店类)
[3.5 编写测试类](#3.5 编写测试类)
[4. 简单工厂方法实线咖啡点餐系统](#4. 简单工厂方法实线咖啡点餐系统)
[4.1 简单工厂方法模式概述](#4.1 简单工厂方法模式概述)
[4.2 引入咖啡工厂](#4.2 引入咖啡工厂)
[4.3 修改 CoffeeStore 咖啡店类逻辑](#4.3 修改 CoffeeStore 咖啡店类逻辑)
[4.4 编写测试类](#4.4 编写测试类)
[4.5 简单工厂方法相较于普通方法的优点](#4.5 简单工厂方法相较于普通方法的优点)
[5. 工厂方法模式实线咖啡点餐系统](#5. 工厂方法模式实线咖啡点餐系统)
[5.1 分析简单工厂模式的缺点,引出工厂方法模式](#5.1 分析简单工厂模式的缺点,引出工厂方法模式)
[5.2 引入抽象咖啡工厂](#5.2 引入抽象咖啡工厂)
[5.3 修改咖啡工厂的代码](#5.3 修改咖啡工厂的代码)
[5.4 编写测试类](#5.4 编写测试类)
[5.5 工厂方法模式的优缺点](#5.5 工厂方法模式的优缺点)
1. 摘要
本篇文章主要讲述23种设计模式中的工厂方法模式。
这里我们用一个咖啡店系统的小案例来引出简单工厂模式的使用,在简单工厂模式的基础上延申介绍工厂方法模式。
2. 需求案例(设计一个咖啡店的点餐系统)
2.1 咖啡父类及其子类
如图所示,我们知道咖啡有很多种,美式咖啡,拿铁咖啡......,所以在这个系统中,不难发现咖啡类Coffee应该定义为父类,又因为所有咖啡都会加糖加奶,所以定义addMilk(),addSugar()方法让子类继承即可,此外咖啡都有不同的名字,所以定义一个抽象方法getName()。
然后让所有准确的咖啡类都继承我们的咖啡父类,使用实线空心三角箭头表示继承关系;
2.2 咖啡店类与咖啡类的关系
咖啡店可以点咖啡,所以定义方法名为 "orderCoffee",方法参数就是顾客点的咖啡名称,方法返回值就是顾客点的咖啡对象;咖啡店类依赖咖啡类,是用虚线箭头指向被依赖类Coffee。
3. 普通方法实线咖啡店点餐系统
这里我们先用最直接粗暴的方式实现上面的咖啡点餐系统,非常简单,主要分为以下几步
3.1 定义Coffee父类
java
public abstract class Coffee {
// 加奶方法
public void addMilk() {
System.out.println("add milk");
}
// 加糖方法
public void addSugar() {
System.out.println("add sugar");
}
// 定义抽象方法,获取咖啡名称,由子类实现
public abstract String getName();
}
3.2 定义美式咖啡类继承Coffee类
java
public class AmericanCoffee extends Coffee{
@Override
public String getName() {
return "美式咖啡";
}
}
3.3 定义拿铁咖啡继承Coffee类
java
public class LatteCoffee extends Coffee{
@Override
public String getName() {
return "拿铁咖啡";
}
}
3.4 定义咖啡店类
java
public class CoffeeStore {
public Coffee orderCoffee(String type) {
Coffee coffee = null;
if ("美式咖啡".equals(type)) {
coffee = new AmericanCoffee();
}else if ("拿铁咖啡".equals(type)) {
coffee = new LatteCoffee();
}else {
throw new RuntimeException("抱歉,不支持这种咖啡");
}
coffee.addMilk();
coffee.addSugar();
return coffee;
}
}
3.5 编写测试类
java
public class TestCoffee {
public static void main(String[] args) {
// 创建咖啡店类
CoffeeStore coffeeShop = new CoffeeStore();
// 点咖啡
Coffee coffee = coffeeShop.orderCoffee("美式咖啡");
System.out.println(coffee.getName());
}
}
运行方法可以看到方法执行已经成功,达到了目的。
但是这种做法有一个问题,就是项目中类与类之间的耦合度太高了,如果现在我再提一个新的需求。
想要添加一个新的咖啡品种,于此带来的影响就是咖啡店类中的"orderCoffee"方法逻辑需要重新做修改,这样做就违背了Java中"对修改关闭,对扩展开发的"的开发原则,是非常不友好的。因此,普通做法虽然简单粗暴,能够以最快的速度达到目的,却忽略了项目的可扩展性与程序的健壮性。
4. 简单工厂方法实线咖啡点餐系统
4.1 简单工厂方法模式概述
简单工厂模式并不属于23种设计模式的一种,但它在实际开发中也比较常用,用的人多了,就成了一种编程习惯,恰好借此机会我们一起来看看简单工厂模式的做法。
简单工厂主要包含以下几种角色:
(1)抽象产品:定义了产品的规范,描述了产品的主要特性和功能,对应咖啡点餐系统中的Coffee父类,父类中定义了加奶和加糖等共有属性;
(2)具体产品:实现或继承了抽象产品的子类,对应咖啡点餐系统中的美式咖啡AmericanCoffee,拿铁咖啡LatteCoffee;
(3)具体工厂:提供创建产品的方法,调用者通过该方法获取产品。具体工厂就是简单工厂方法中我们要新增的工厂类。
4.2 引入咖啡工厂
如下图所示,我们做项目总是调侃一句话,没有什么问题是加一层无法解决的,如果解决不了,就加两层......
在简单工厂方法中,我们就需要在Coffee咖啡类和CoffeStore咖啡店中间加一层,创建 SimpleCoffeeFactory 咖啡工厂类,由咖啡工厂负责生产咖啡,当咖啡店有人点餐时,直接调用咖啡工厂的 createCoffee() 创建咖啡方法,方法返回值为Coffee。
对比普通普通方法,我们需要创建SimpleCoffeeFactory咖啡工厂类,再将CoffeeStore咖啡店类中点咖啡方法做修改,如下所示
SimpleCoffeeFactor 咖啡工厂类;
java
public class SimpleCoffeeFactory {
public Coffee createCoffee(String type) {
Coffee coffee = null;
if ("美式咖啡".equals(type)) {
coffee = new AmericanCoffee();
}else if ("拿铁咖啡".equals(type)) {
coffee = new LatteCoffee();
}else {
throw new RuntimeException("抱歉,不支持这种咖啡");
}
// 这里我们生产完毕咖啡直接返回,将加糖或加奶的决定权交给顾客
return coffee;
}
}
4.3 修改 CoffeeStore 咖啡店类逻辑
可以看到,我们将咖啡类和咖啡店类进行解耦,让咖啡工厂作为二者的中间桥梁,如果后续我们要添加其他品种的咖啡,直接修改咖啡工厂的代码逻辑即可,咖啡类Coffee和咖啡店类CoffeeStore都不会受到任何影响。
而且,我们给了顾客选择,
顾客只想加糖,就调用 orderCoffeeOnlySugar 方法;
顾客只想加奶,就调用 orderCoffeeOnlyMilk 方法;
如果都不想加,可以再创建另外一个方法,极大地简化了代码量。
java
public class CoffeeStore {
// 创建一个咖啡工厂的对象
SimpleCoffeeFactory factory = new SimpleCoffeeFactory();
public Coffee orderCoffeeOnlySugar(String type) {
Coffee coffee = factory.createCoffee(type);
coffee.addSugar();
return coffee;
}
public Coffee orderCoffeeOnlyMilk(String type) {
Coffee coffee = factory.createCoffee(type);
coffee.addMilk();
return coffee;
}
}
4.4 编写测试类
只需要创建 SimpleCoffeeFactory 咖啡工厂对象,我们就可以调用咖啡工厂对象的方法,传入我们希望得到的咖啡,此时,咖啡类和咖啡店类就完成了解耦合。
java
public static void main(String[] args) {
SimpleCoffeeFactory factory = new SimpleCoffeeFactory();
// 点咖啡
Coffee coffee = factory.createCoffee("拿铁咖啡");
System.out.println(coffee.getName());
System.out.println("--------------------------------");
Coffee coffee1 = factory.createCoffee("美式咖啡");
System.out.println(coffee1.getName());
}
4.5 简单工厂方法相较于普通方法的优点
如果我们采用最原始的方法点一杯只加糖和只加奶的咖啡,代码逻辑如下,可以看到,每当用户点一杯咖啡,我们就要写一次咖啡的判断逻辑并创建咖啡,非常麻烦。
因此,我们就可以将公共的判断咖啡种类和创建咖啡的公共部分抽取出来,交给咖啡工厂去完成,这样一来就可以节省大量代码 ,使项目中各部分的代码耦合度降低;
java
public class CoffeeStore {
public Coffee orderCoffeeOnlySugar(String type) {
Coffee coffee = null;
if ("美式咖啡".equals(type)) {
coffee = new AmericanCoffee();
}else if ("拿铁咖啡".equals(type)) {
coffee = new LatteCoffee();
}else {
throw new RuntimeException("抱歉,不支持这种咖啡");
}
coffee.addSugar();
return coffee;
}
public Coffee orderCoffeeOnlyMilk(String type) {
Coffee coffee = null;
if ("美式咖啡".equals(type)) {
coffee = new AmericanCoffee();
}else if ("拿铁咖啡".equals(type)) {
coffee = new LatteCoffee();
}else {
throw new RuntimeException("抱歉,不支持这种咖啡");
}
coffee.addMilk();
return coffee;
}
5. 工厂方法模式实线咖啡点餐系统
5.1 分析简单工厂模式的缺点,引出工厂方法模式
刚才我们使用了简单工厂方法实线了点咖啡的案例,但我们也可以发现这种方法的缺点,就是仍然违背了 "对修改开发,对扩展封闭" 的原则。
使用了咖啡工厂之后,我们只需要将生产咖啡交给工厂来完成。但是,如果我们要增加一种新的咖啡,还是需要修改咖啡工厂中的代码逻辑,我们将新需求带来的影响缩小到了咖啡工厂这个类中,但还是需要做修改,违背了开闭原则。
但如果我们使用工厂方法模式 ,就不会违背开闭原则,它的做法是定义一个创建对象的接口,让子类决定实例化哪种产品。
工厂方法模式的主要角色:
(1)抽象工厂(Abstract Factory):提供创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
(2)具体工厂(Concreate Factory):主要实现抽象工厂中的抽象方法,完成产品的创建。
(3)抽象产品(Abstract Product):定义产品的规范,描述了产品的主要功能和特性。
(4)具体产品(Concreate Product):实线了抽象产品角色定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
5.2 引入抽象咖啡工厂
在简单工厂方法模式中,我们将生产咖啡提取成一个工厂,让工厂生产咖啡。但实际上,咖啡工厂仍然可以做进一步的抽象,让子类去实线抽象工厂中的方法。这样一来,当我们需要新增一种咖啡时,只需要新增一个咖啡工厂的实现类,其他的都不需要做任何改变。
5.3 修改咖啡工厂的代码
对比简单工厂,抽象咖啡类Coffee,子类AmericanCoffee和LatteCoffee都不用改变;
新建抽象咖啡工厂类:
java
public interface CoffeeFactory {
// 创建咖啡对向的方法
AmericanCoffee createCoffee();
}
创建美式咖啡工厂类实线抽象咖啡工厂接口,此工厂专门生产美式咖啡:
java
public class AmericanCoffeeFactory implements CoffeeFactory{
@Override
public AmericanCoffee createCoffee() {
return new AmericanCoffee();
}
}
创建拿铁咖啡工厂类实线抽象咖啡工厂接口,此工厂专门生产拿铁咖啡:
java
public class LatteCoffeeFactory implements CoffeeFactory{
@Override
public LatteCoffee createCoffee() {
return new LatteCoffee();
}
}
修改咖啡店类点咖啡的代码逻辑,这里几乎没有很大变化,关键点在于创建的咖啡工厂对象为顶层父接口对象,我们只需要通过父接口对象调用 createCoffee() 对象;
java
public class CoffeeStore {
private CoffeeFactory coffeeFactory;
public void setCoffeeFactory(CoffeeFactory coffeeFactory) {
this.coffeeFactory = coffeeFactory;
}
public Coffee createCoffee(String orderType) {
Coffee coffee = coffeeFactory.createCoffee();
coffee.addMilk();
coffee.addSugar();
return coffee;
}
}
5.4 编写测试类
java
public static void main(String[] args) {
// 创建咖啡店对象
CoffeeStore store = new CoffeeStore();
// 创建拿铁咖啡工厂对象,父类引用指向子类对象
CoffeeFactory latteCoffeeFactory = new LatteCoffeeFactory();
store.setCoffeeFactory(latteCoffeeFactory);
// 点咖啡
Coffee coffee = store.createCoffee("拿铁咖啡");
System.out.println(coffee.getName());
}
运行测试类,我们就会得到拿铁咖啡工厂生产的拿铁咖啡
5.5 工厂方法模式的优缺点
**优点:**实线了代码之间的解耦,模块之间耦合度降低。当系统要添加新的产品类时,只需要添加具体产品类和具体工厂类,无需对原有工厂作出修改,满足开闭原则。
举例:当我们想要增加新品种的咖啡时(比如香草咖啡),只需要在创建一个香草咖啡工厂去实现咖啡工厂接口;再创建一个香草咖啡类继承咖啡父类,不需要对以往的代码作出修改,只在原有的代码上做增加。
**缺点:**每增加一种产品,就需要增加一个产品工厂类和一个具体产品类,随着产品越来越多,会导致系统中的代码越来越多越来越复杂,增加了系统的复杂度,不易维护。