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

发布于:2024-05-06 ⋅ 阅读:(21) ⋅ 点赞:(0)

目录

1. 摘要

2. 需求案例(设计一个咖啡店的点餐系统)

2.1 咖啡父类及其子类

2.2 咖啡店类与咖啡类的关系

3. 普通方法实线咖啡店点餐系统

3.1 定义Coffee父类

3.2 定义美式咖啡类继承Coffee类

3.3 定义拿铁咖啡继承Coffee类

3.4 定义咖啡店类

3.5 编写测试类

4. 简单工厂方法实线咖啡点餐系统

4.1 简单工厂方法模式概述

4.2 引入咖啡工厂

4.3 修改 CoffeeStore 咖啡店类逻辑

4.4 编写测试类

4.5 简单工厂方法相较于普通方法的优点

5. 工厂方法模式实线咖啡点餐系统

5.1 分析简单工厂模式的缺点,引出工厂方法模式

5.2 引入抽象咖啡工厂

5.3 修改咖啡工厂的代码

 5.4 编写测试类

5.5 工厂方法模式的优缺点


1. 摘要

本篇文章主要讲述23种设计模式中的工厂方法模式。

这里我们用一个咖啡店系统的小案例来引出简单工厂模式的使用,在简单工厂模式的基础上延申介绍工厂方法模式。

2. 需求案例(设计一个咖啡店的点餐系统)

2.1 咖啡父类及其子类

如图所示,我们知道咖啡有很多种,美式咖啡,拿铁咖啡......,所以在这个系统中,不难发现咖啡类Coffee应该定义为父类,又因为所有咖啡都会加糖加奶,所以定义addMilk(),addSugar()方法让子类继承即可,此外咖啡都有不同的名字,所以定义一个抽象方法getName()

然后让所有准确的咖啡类都继承我们的咖啡父类,使用实线空心三角箭头表示继承关系;

2.2 咖啡店类与咖啡类的关系

咖啡店可以点咖啡,所以定义方法名为 "orderCoffee",方法参数就是顾客点的咖啡名称,方法返回值就是顾客点的咖啡对象;咖啡店类依赖咖啡类,是用虚线箭头指向被依赖类Coffee。

3. 普通方法实线咖啡店点餐系统

这里我们先用最直接粗暴的方式实现上面的咖啡点餐系统,非常简单,主要分为以下几步

3.1 定义Coffee父类
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类
public class AmericanCoffee extends Coffee{
    @Override
    public String getName() {
        return "美式咖啡";
    }
}
3.3 定义拿铁咖啡继承Coffee类
public class LatteCoffee extends Coffee{
    @Override
    public String getName() {
        return "拿铁咖啡";
    }
}
3.4 定义咖啡店类
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 编写测试类
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 咖啡工厂类;

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 方法;

如果都不想加,可以再创建另外一个方法,极大地简化了代码量。

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 咖啡工厂对象,我们就可以调用咖啡工厂对象的方法,传入我们希望得到的咖啡,此时,咖啡类和咖啡店类就完成了解耦合。

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 简单工厂方法相较于普通方法的优点

如果我们采用最原始的方法点一杯只加糖和只加奶的咖啡,代码逻辑如下,可以看到,每当用户点一杯咖啡,我们就要写一次咖啡的判断逻辑并创建咖啡,非常麻烦。

因此,我们就可以将公共的判断咖啡种类和创建咖啡的公共部分抽取出来,交给咖啡工厂去完成,这样一来就可以节省大量代码使项目中各部分的代码耦合度降低

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都不用改变;

新建抽象咖啡工厂类:

public interface CoffeeFactory {
    // 创建咖啡对向的方法
    AmericanCoffee createCoffee();
}

创建美式咖啡工厂类实线抽象咖啡工厂接口,此工厂专门生产美式咖啡:

public class AmericanCoffeeFactory implements CoffeeFactory{
    @Override
    public AmericanCoffee createCoffee() {
        return new AmericanCoffee();
    }
}

创建拿铁咖啡工厂类实线抽象咖啡工厂接口,此工厂专门生产拿铁咖啡:

public class LatteCoffeeFactory implements CoffeeFactory{
    @Override
    public LatteCoffee createCoffee() {
        return new LatteCoffee();
    }
}

修改咖啡店类点咖啡的代码逻辑,这里几乎没有很大变化,关键点在于创建的咖啡工厂对象为顶层父接口对象,我们只需要通过父接口对象调用 createCoffee() 对象;

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 编写测试类
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 工厂方法模式的优缺点

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

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

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