设计模式(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{@Overridepublic String getName() {return "美式咖啡";}
}
3.3 定义拿铁咖啡继承Coffee类
public class LatteCoffee extends Coffee{@Overridepublic 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{@Overridepublic AmericanCoffee createCoffee() {return new AmericanCoffee();}
}

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

public class LatteCoffeeFactory implements CoffeeFactory{@Overridepublic 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.mzph.cn/diannao/9171.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

故障——蓝桥杯十三届2022国赛大学B组真题

问题分析 这道题纯数学&#xff0c;考察贝叶斯公式 AC_Code #include <bits/stdc.h> using namespace std; typedef pair<int,double> PI; bool cmp(PI a,PI b){if(a.second!b.second)return a.second>b.second;return a.first<b.first; } int main() {i…

Java毕设之基于springboot的医护人员排班系统

运行环境 开发语言:java 框架:springboot&#xff0c;vue JDK版本:JDK1.8 数据库:mysql5.7(推荐5.7&#xff0c;8.0也可以) 数据库工具:Navicat11 开发软件:idea/eclipse(推荐idea) 系统详细实现 医护类型管理 医护人员排班系统的系统管理员可以对医护类型添加修改删除以及…

2024年大学生三下乡社会实践活动投稿注意事项

随着2024年夏季的热浪一同涌来的,是我校一年一度的“大学生三下乡”社会实践活动。作为一名积极参与其中的大学生,我满怀激情地投身于这项旨在促进农村发展的公益行动中。然而,当活动圆满落幕,轮到我承担起向各大媒体投稿、传播实践成果的重任时,却遭遇了一系列意想不到的挑战,…

端口占用解决方法

1、查询端口 打开cmd命令提示符窗口&#xff0c;输入以下指令查询所有端口 netstat -ano //查询所有端口 netstat -ano|findstr 8080 //查询指定端口 2、杀死进程 taskkill /t /f /im 进程号(PID)

【Ansible】ansible-playbook剧本

playbook 是ansible的脚本 playbook的组成 1&#xff09;Tasks&#xff1a;任务&#xff1b;通过tasks 调用ansible 的模板将多个操作组织在一个playbook中运行 2&#xff09;Variables&#xff1a;变量 3&#xff09;Templates&#xff1a;模板 4&#xff09;Handles&#xf…

车载测试系列:入行车载测试分享

车载测试前景如何&#xff1f; 软件定义汽车时代的发展趋势&#xff0c;随着控制器自主开发力度的加强&#xff0c;作为V流程中必备环节&#xff0c;车载测试工程师岗位需求会越来越多&#xff1b;控制器集成化&#xff0c;功能集成程度越来越高&#xff0c;对于测试工程师的知…

度小满——征信报告图建模

目录 背景介绍 发展趋势 技术演进 图在金融风控领域中的演进 度小满图机器学习技术体系 案例 征信报告介绍 征信报告图建模

Vuex 和 Pinia 两个状态管理模式的区别

Pinia和Vuex一样都是是vue的全局状态管理器。其实Pinia就是Vuex5&#xff0c;只不过为了尊重原作者的贡献就沿用了这个看起来很甜的名字Pinia。&#xff08;实际项目中千万不要即用Vuex又用Pinia&#xff0c;不然你会被同事‘’请去喝茶的‘’。 一、安装&#xff08;常用命令安…

JavaScript百炼成仙自学笔记——13

函数七重关之六&#xff08;“new”一个函数&#xff09; 看个代码&#xff1a; function hello(){console.log(this); } 1、this&#xff1a;也是JavaScript中的一个关键字&#xff0c;永远指向当前函数的调用者 解释一下,有两层意思&#xff1a; ①this要嘛不出现&#…

深入理解 Linux 文件系统与动静态库

目录 一、Linux 文件系统中的 inode 二、软硬链接 三、动静态库 在 Linux 系统中&#xff0c;文件系统和动静态库是非常重要的概念。本文将带大家深入了解这些内容&#xff0c;让你在技术之路上更进一步。 一、Linux 文件系统中的 inode 何为文件系统&#xff1f;对计算机中…

通用型产品发布解决方案(基础环境搭建)

文章目录 1.项目技术栈和前置技术2.创建Linux平台1.需求分析2.安装Virtual Box1.BIOS里修改设置开启虚拟化设备支持&#xff08;f2 或f10&#xff09;2.任务管理器 -> cpu 查看虚拟化是否开启3.卸载方式4.安装6.1.265.管理员身份运行&#xff0c;选择安装位置6.一直下一步&a…

PHPStudy 访问网页 403 Forbidden禁止访问

涉及靶场 upload-labd sqli-labs pikachu dvwa 以及所有部署在phpstudy中的靶场 注意&#xff1a;一定要安装解压软件 很多同学解压靶场代码以后访问报错的原因是&#xff1a;电脑上没有解压软件。 这个时候压缩包看起来就是黄色公文包的样子&#xff0c;右键只有“全部提取…

大厂Java面试题:MyBatis是如何进行分页的?分页插件的实现原理是什么?

大家好&#xff0c;我是王有志。 今天给大家带来的是一道来自京东的关于 MyBatis 实现分页功能的面试题&#xff1a;MyBatis是如何进行分页的&#xff1f;分页插件的实现原理是什么&#xff1f;通常&#xff0c;分页的方式可以分为两种&#xff1a; 逻辑&#xff08;内存&…

如何开启深色模式【攻略】

如何开启深色模式【攻略】 前言版权推荐如何开启深色模式介绍手机系统手机微信手机QQ手机快手手机抖音 电脑系统电脑微信电脑QQ电脑WPS电脑浏览器 最后 前言 2024-5-9 20:48:21 深色模式给人以一种高级感。 本文介绍一些常用软件深色模式的开启 以下内容源自《【攻略】》 仅…

从零开始写 Docker(十三)---实现 mydocker rm 删除容器

本文为从零开始写 Docker 系列第十三篇&#xff0c;实现类似 docker rm 的功能&#xff0c;使得我们能够删除容器。 完整代码见&#xff1a;https://github.com/lixd/mydocker 欢迎 Star 推荐阅读以下文章对 docker 基本实现有一个大致认识&#xff1a; 核心原理&#xff1a;深…

谷歌CEO最新访谈:AI浪潮仍处于早期阶段,公司未来最大威胁是执行力不足

作为搜索领域无可争议的霸主&#xff0c;谷歌改变了我们生活的方方面面&#xff0c;从日常琐事到工作事务&#xff0c;再到我们的沟通方式。多年来&#xff0c;谷歌一直是互联网的窗口&#xff0c;为我们提供大量知识和信息&#xff0c;但如今&#xff0c;随着其他类似平台的崛…

HarmonyOS开发之ArkTS使用:用户登录页面应用

目录 目录 前言 关于HarmonyOS 环境准备 新建项目 设计用户登录页面 1. 布局设计 2. 编写ArkTS代码 运行和测试 结束语 前言 随着HarmonyOS&#xff08;鸿蒙操作系统&#xff09;的不断发展&#xff0c;越来越多的开发者开始投入到这个全新的生态系统中&#xff0c;而…

车载测试系列:车载测试Bug定位与分析

车载Bug定位 1.1 模拟复现条件 能够复现bug才能对问题进行定位、解决以及验证。找到bug存在的特定的条件&#xff0c;进行重现。对于依赖外部输入的条件&#xff0c;如果条件比较复杂难以模拟可以在程序里预设直接进入对应状态。 1.2 打印Log 根据问题的现象&#xff0c;在…

Spring-依赖来源

依赖来源 1 Spring BeanDefinition&#xff08;xml,注解&#xff0c;BeanDefinitionBuilder, 还有API实现的单例对象&#xff09; 2 Spring 内建BeanDefinition 3 内建单例对象 依赖注入和依赖查找的区别 Context.refresh() 的时候会调用这个方法&#xff1a;prepareBeanF…

扩展学习|结合故事的力量和数字的力量:混合方法研究和混合研究综述

文献来源&#xff1a;Pluye, Pierre, and Quan Nha Hong. "Combining the power of stories and the power of numbers: mixed methods research and mixed studies reviews." Annual review of public health 35 (2014): 29-45. 文献获取&#xff1a;链接&#xff1…