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

目录

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/608369.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

影响视频视觉质量的因素——各类视觉伪影

模糊效应(Blurring Artifact) 图像模糊(blurring):平滑图像的细节和边缘产生的现象,模糊对于图像来说,是一个低通滤波器(low-pass filter)。一般而言,用户更…

VisualGDB:Linux静态库项目创建、编译及库的使用

接上篇《VisualGDB:Linux动态库项目创建、编译及库的使用》,静态库的创建和使用与动态库基本无差别,唯一需要做的就是指定项目生成静态库。 一、指定项目生成静态库 二、重新构建和编译项目 这里注意,同样要copy一个libxxx.so格式…

服务器数据恢复—RAID5磁盘阵列两块盘离线的数据恢复过程

服务器故障: 服务器中有一组由多块硬盘组建的raid5磁盘阵列,服务器阵列中2块硬盘先后掉线导致服务器崩溃。 服务器数据恢复过程: 1、将故障服务器中所有磁盘编号后取出,由硬件工程师对掉线的两块磁盘进行物理故障检测&#xff0c…

Linux 文件

文章目录 文件操作回顾(C/C)系统调用接口 管理文件认识一切皆文件C/C的文件操作函数与系统调用接口的关系……重定向与缓冲区 -- 认识重定向与缓冲区 -- 理解使用重定向缓冲区实现一个简单的Shell(加上重定向)标准输出和标准错误(在重定向下的意义) 磁盘文件磁盘存储文件操作系…

聊天框 - 微信加载历史数据的效果原来这样实现的

原文:https://juejin.cn/post/7337114587123335180?searchId20240509192958AF7D129567F92AD7E083 公众号:程序员白特,欢迎一起交流学习~ 前言 我记得2021年的时候做过聊天功能,那时业务也只限微信小程序 那时候的心路历程是&am…

win7开启远程桌面却连接不上,如何解决Win7系统开启远程桌面但无法连接的问题

在使用Win7系统时,有时候我们可能会遇到这样的问题:已经成功开启了远程桌面功能,但尝试连接时却总是失败。这可能是由于多种原因导致的,下面我们将详细分析并提供相应的解决方案。 确保本地网络连接正常 可以尝试通过Ping命令测试…

【start和run的区别(面试题)及创建线程的五种写法】

线程 1.start和run的区别2.创建线程的五种写法1.继承Thread,重写run2.实现runnable,重写run3.继承Thread,重写run,使用匿名内部类4.实现Runnable,重写run,使用匿名内部类5.使用lambda表达式 1.start和run的区别 1.start方法内部,是会调用到系统api&…

MATLAB 三维空间中在两点之间等间隔插入多个点 (67)

MATLAB 三维空间中在两点之间等间隔插入多个点 (67) 一、算法介绍二、算法实现1.代码2.结果一、算法介绍 用于加密直线点云,具体为根据给定的直线端点,沿着该直线方向,插入多个点,从而加密。具体方法和效果如下所示: 二、算法实现 1.代码 代码如下(示例): % 定…

融知财经:期货在哪里可以交易?期货交易有哪些交易规则?

作为当前金融市场的一种投资方式,期货只适合一些投资者,比如想获得高收益的投资者,因为期货的风险系数很高。但是很多投资者还不知道期货的意思,在一个固定的交易场所,期货是买卖标准化商品或金融资产的远期合约的交易…

SAP sq01,sq02,sq03创建query报表

步骤:1,SQ03创建用户组(User Group) 2,SQ02创建信息集(InfoSet) 3,SQ03分配用户和InfoSet 4,SQ01创建查询 5,SE93给Query分配Tcode 1,SQ03创建用…

pikachu靶场搭建(保姆级,手把手教学)

(phpstudy安装pikachu配置) 1.下载phpstudy(以Windows系统为例) 下载地址:https://www.xp.cn/download.html 1.打开网址 2.点击立即下载 3.选择适合自己的版本 查看自己电脑版本: 打开设置找到系统点击…

effective python学习笔记_函数

函数返回值尽量不要超过三个 局限性:当返回参数过多时,有时会搞混哪个是哪个,可能返回的两个值反了 解决方法:如果参数过多,可以组装*变量返回,或者自定义轻量类型或namedtuple返回 有意外情况时尽量抛异…

Kubernetes容器技术详解

kubernetes Kubernetes(K8s)由Google打造,是一款功能强大、灵活可扩展的容器编排平台,引领云原生技术潮流。 Kubernetes主要解决以下4大点: 1.自动化运维平台 如下图所示: Kubernetes携手Docker&#xf…

2024牛客五一集训派对day2 Groundhog Looking Dowdy 个人解题思路

前言: 被实验室教练要求要打的这次五一牛客的训练赛,这些区域赛难度的题对于大一的我来说难度实在是太高了,我和我的队友只写了一些非常简单的签到题,其他题目都没怎么看(我们太弱了),但我可以分…

Powerdesigner导入mysql8之后注释丢失

目录 一、问题描述及解决思路 二、导入的步骤 1.先按正常步骤建立一个物理数据模型 (1)点击“文件-新建模型” (2)选择物理模型和数据库 2.从sql文件导入表 (1)点击“数据库-Update Model from Data…

智慧营销的未来:中国AIGC技术的演进与应用 #未来是现在的趋势#

📑前言 随着人工智能(AI)技术的蓬勃发展,尤其是在营销技术(MarTech)领域,AIGC(AI Generated Content)技术在中国市场的应用和影响日益显著。2023年,中国在AIG…

16-LINUX--线程安全

一。线程安全 线程安全即就是在多线程运行的时候,不论线程的调度顺序怎样,最终的结果都是 一样的、正确的。那么就说这些线程是安全的。 要保证线程安全需要做到: 1) 对线程同步,保证同一时刻只有一个线程访问临界资…

什么是静态住宅代理IP?

静态住宅代理(也称为静态ISP代理)是最流行的代理类型之一。它们也是隐藏您的身份并保持在线匿名的最佳方法之一。您为什么要使用住宅代理而不是仅使用常规代理服务?下面我具体分享。 一、什么是静态住宅代理? 首先,我…

vivado 低级别 SVF JTAG 命令

低级别 SVF JTAG 命令 注释 : 在 Versal ™ 器件上不支持 SVF 。 低级别 JTAG 命令允许您扫描多个 FPGA JTAG 链。针对链操作所生成的 SVF 命令使用这些低级别命令来访问链中的 FPGA 。 报头数据寄存器 (HDR) 和报头指令寄存器 (HIR) 语法 HDR length […