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

news2024/12/24 22:10:15

目录

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.coloradmin.cn/o/1657450.html

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

相关文章

大模型微调实战之强化学习 贝尔曼方程及价值函数(五)

大模型微调实战之强化学习 贝尔曼方程及价值函数(五) 现在, 看一下状态-动作值函数的示意图: 这个图表示假设首先采取一些行动(a)。因此,由于动作(a),代理可能会被环境转换到这些状…

docker Harbor私有仓库部署管理

搭建本地私有仓库,但是本地私有仓库的管理和使用比较麻烦,这个原生的私有仓库并不好用,所以我们采用harbor私有仓库,也叫私服,更加人性化。 一、什么是Harbor Harbor是VWware 公司开源的企业级Docker Registry项…

ESP8266-01s刷入固件报SP8266 Chip efuse check error esp_check_mac_and_efuse

一、遇到的问题 使用ESP8266 固件烧录工具flash_download_tools_v3.6.8 烧录固件报错: 二、解决方法 使用espressif推出发基于python的底层烧写工具:esptool 安装方法:详见https://docs.espressif.com/projects/esptool/en/latest/esp32/ …

腾讯游戏海外扩张,增持芬兰游戏开发商股份持股比例增至14.8%

易采游戏网5月8日消息,近日腾讯再次出手,大幅增持了芬兰知名游戏开发商Remedy Entertainment的股份,持股比例猛增至14.8%。这一举动引起了业界和投资者的广泛关注。 据了解,腾讯此次增持是在2024年4月24日完成的。根据芬兰法律规…

Pandas高效化运算与时间序列处理

文章目录 第1关:字符串操作方法第2关:Pandas的日期与时间工具第3关:Pandas时间序列的高级应用 第1关:字符串操作方法 任务描述 本关任务:读取step1/bournemouth_venues.csv文件,获取Venue Name列&#xff…

【C++】string类的使用②(容量接口Capacity || 元素获取Element access)

🔥个人主页: Forcible Bug Maker 🔥专栏: STL || C 目录 前言🔥容量接口(Capacity)size和lengthcapacitymax_sizereserveresizeclearemptyshrink_to_fit 🔥元素获取(Ele…

接口测试及常用的接口测试工具(Postman/Jmeter)

🍅 视频学习:文末有免费的配套视频可观看 🍅 点击文末小卡片 ,免费获取软件测试全套资料,资料在手,涨薪更快 首先,什么是接口呢? 接口一般来说有两种,一种是程序内部的接…

phpstudy靶场访问显示404 Not Found

涉及靶场 upload-labd sqli-labs pikachu dvwa 以及所有部署在phpstudy中的靶场 一、检查phpstduy设置 localhost——管理——修改 1、根目录(默认设置,不要改) localhost这个域名必须保留,并且把根目录设置为phpstudy的WWW文…

Duplicate entry ‘asdfg‘ for key ‘clazz.name‘

Mybatis:java.sql.SQLIntegrityConstraintViolationException:Duplicate entry ‘asdfg’ for key ‘clazz.name’ 违反了数据库的唯一约束条件,即插入数据的时候具有唯一约束(被unique修饰)的列值重复了 在修改的过程中发生错误,…

WPF控件之StackPanel布局控件

StackPanel别名堆栈panel 使其子元素按照一定方式进行布局&#xff0c;子元素排布方式要么设置为水平排布&#xff0c;要么垂直排布。 属性 Orientation设置排列方式(默认的是垂直排布) : Horizontal水平排布 Vertical 垂直排布 实例 <StackPanel Orientation"Vert…

漏洞管理是如何在攻击者之前识别漏洞从而帮助人们阻止攻击的

漏洞管理 是主动查找、评估和缓解组织 IT 环境中的安全漏洞、弱点、差距、错误配置和错误的过程。该过程通常扩展到整个 IT 环境&#xff0c;包括网络、应用程序、系统、基础设施、软件和第三方服务等。鉴于所涉及的高成本&#xff0c;组织根本无法承受网络攻击和数据泄露。如果…

泛微E9开发 通过点击按钮来复制选择的明细行

泛微E9开发 通过点击按钮来复制选择的明细行 复制明细行功能背景展示效果实现方法 复制明细行 功能背景 用户可以通过“复制明细”按钮来实现新增选择的明细行&#xff0c;并且新增明细行的数据跟选择的数据完全一样&#xff0c;具体操作如下图所示&#xff1a; 手动新增明细…

[Collection与数据结构] Map与Set(一):二叉搜索树与Map,Set的使用

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏:&#x1f355; Collection与数据结构 (91平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm1001.2014.3001.5482 &#x1f9c0;Java …

程序设计——前后端分离实现简单表白墙

文章目录 一、前端页面样式代码二、前后端衔接1. 后端创建 maven 项目2. 针对前后端交互的解释以及后端代码的实现针对 post 请求解释前后端衔接针对 Get 请求解释前后端衔接 3.后端与数据库的联系以及对数据的存取单独封装数据库连接代码解释后端存储 save 数据的代码解释后端…

【因特网中自治系统内部的路由选择,RIP 进程处理 OSPFOSPF(Open Shortest Path First)最短路径优先协议】

文章目录 因特网中自治系统内部的路由选择RIP&#xff08;Routing Information Protocol&#xff09;内部网关协议RIP通告&#xff08;advertisements&#xff09;RIP: 链路失效和恢复RIP 进程处理OSPF(Open Shortest Path First)最短路径优先协议OSPF “高级” 特性(在RIP中的…

吴恩达机器学习笔记 三十七 电影推荐系统 使用特征 成本函数 协同过滤算法

以电影评分系统为例&#xff0c;令 r(i, j) 来表示用户 j 已经对电影 i 评分&#xff0c; y&#xff08;i, j&#xff09;表示评分具体是多少。 假如每部电影有自己的特征&#xff0c;那么用户 j 对电影 i 的评分预测为 w(j) * x(i) b(j) r(i, j) &#xff1a;一个用户 j 是否…

mysql: docker 异常 - mbind: Operation not permitted

mbind: Operation not permitted 前言&#xff1a;正文:结论 &#xff1a; 前言&#xff1a; 用数据库处理平台问题今天报错&#xff0c;mbind: Operation not permitted。 mbind 不允许操作&#xff0c;一头雾水这是什么意思。 网上找了很多资料大概意思是&#xff1a; 这个错…

MATLAB 点云随机赋色 (68)

MATLAB 点云随机赋色 (68) 一、算法介绍二、算法介绍1.代码2.结果三、数据链接一、算法介绍 读取的点云本身带有颜色信息,有时我们需要为每个点随机赋予一种颜色,下面是具体效果和实现代码,以及使用的数据: 二、算法介绍 1.代码 代码如下(示例): % 读取点云文件 f…

Windows如何安装hadoop

var code "da0f4508-813e-4f6c-b5e8-6c19f92be6d1"Hadoop是一个开源的分布式计算平台&#xff0c;旨在处理大规模数据的存储和处理。它提供了分布式文件系统&#xff08;HDFS&#xff09;和分布式计算框架&#xff08;MapReduce&#xff09;&#xff0c;使得用户能够…

12 华三的二层链路聚合

12 华三的二层链路聚合 配置思路 1. 配置二层静态聚合组 (1) 进入系统视图。 system-view (2) 创建二层聚合接口&#xff0c;并进入二层聚合接口视图。 interface bridge-aggregation interface-number [ lite ] 创建二层聚合接口后&#xff0c;系统将自动生成…