设计模式(2)——工厂方法模式

目录

[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 工厂方法模式的优缺点

**优点:**实线了代码之间的解耦,模块之间耦合度降低。当系统要添加新的产品类时,只需要添加具体产品类和具体工厂类,无需对原有工厂作出修改,满足开闭原则。

举例:当我们想要增加新品种的咖啡时(比如香草咖啡),只需要在创建一个香草咖啡工厂去实现咖啡工厂接口;再创建一个香草咖啡类继承咖啡父类,不需要对以往的代码作出修改,只在原有的代码上做增加。

**缺点:**每增加一种产品,就需要增加一个产品工厂类和一个具体产品类,随着产品越来越多,会导致系统中的代码越来越多越来越复杂,增加了系统的复杂度,不易维护。

相关推荐
蜡笔小新..7 小时前
【设计模式】软件设计原则——开闭原则&里氏替换&单一职责
java·设计模式·开闭原则·单一职责原则
性感博主在线瞎搞9 小时前
【面向对象】设计模式概念和分类
设计模式·面向对象·中级软件设计师·设计方法
lucifer3119 小时前
JavaScript 中的组合模式(十)
javascript·设计模式
lucifer3119 小时前
JavaScript 中的装饰器模式(十一)
javascript·设计模式
蜡笔小新..9 小时前
【设计模式】软件设计原则——依赖倒置&合成复用
设计模式·依赖倒置原则·合成复用原则
刷帅耍帅9 小时前
设计模式-代理模式
设计模式·代理模式
神的孩子都在歌唱17 小时前
行为设计模式 -观察者模式- JAVA
java·观察者模式·设计模式
刷帅耍帅21 小时前
设计模式-解释器模式
设计模式·解释器模式
刷帅耍帅21 小时前
设计模式-备忘录模式
设计模式·备忘录模式