程序员必修必炼的设计模式之工厂模式

news2024/12/22 8:38:03

本文首发自「慕课网」(www.imooc.com),想了解更多IT干货内容,程序员圈内热闻,欢迎关注"慕课网"或慕课网公众号!

作者:李一鸣 | 慕课网讲师


工厂模式是平时开发过程中最常见的设计模式。工厂模式解决类的实例化问题,它属于创建型模式。工厂模式也经常会和其他设计模式组合使用。

试想你去麦当劳买一个汉堡。你只需要告诉收银员要一个xx汉堡。过一会就会有一个此类型的汉堡被制作出来。而你完全不需要知道这个汉堡是怎么被制作出来的。这个例子中你就是客户端代码,麦当劳就是工厂,负责生产汉堡。汉堡是接口,而具体的某一种汉堡,比如说香辣鸡腿堡,就是实现了汉堡接口的类。

我们继续通过另外一个例子,深入理解工厂模式。现在我们给某款音乐软件开发一个推荐功能。需求是能够根据用户选择的音乐风格,推荐不同风格的歌曲清单。那么你打算怎么实现呢?

1. 音乐推荐器1.0版本

如果之前没有学习过设计模式,很可能你的实现会是这样。编写 RecommendMusicService 类,里面有一个 Recommend方法。根据输入的风格不同,执行不同的推荐逻辑。代码如下:

public class RecommendMusicService {
    public List<String> recommend(String style) {
        List<String> recommendMusicList = new ArrayList<>();

        if ("metal".equals(style)) {
            recommendMusicList.add("Don't cry");
        } else if ("country".equals(style)) {
            recommendMusicList.add("Hotel california");
        } else if ("grunge".equals(style)) {
            recommendMusicList.add("About a girl");
        }else {
            recommendMusicList.add("My heart will go on");
        }

        return recommendMusicList;
    }
}

是不是觉得 recommed 方法太长了? OK,我们重构下,把每种音乐风格的推荐逻辑封装到相应的方法中。这样推荐方法就可以复用了。

public class RecommendMusicService {
    public List<String> recommend(String style) {
        List<String> recommendMusicList = new ArrayList<>();

        if ("metal".equals(style)) {
            recommendMetal(recommendMusicList);
        } else if ("country".equals(style)) {
            recommendCountry(recommendMusicList);
        } else if ("grunge".equals(style)) {
            recommendGrunge(recommendMusicList);
        }else {
            recommendPop(recommendMusicList);
        }

        return recommendMusicList;
    }

    private void recommendPop(List<String> recommendMusicList) {
        recommendMusicList.add("My heart will go on");
        recommendMusicList.add("Beat it");
    }

    private void recommendGrunge(List<String> recommendMusicList) {
        recommendMusicList.add("About a girl");
        recommendMusicList.add("Smells like teen spirit");
    }

    private void recommendCountry(List<String> recommendMusicList) {
        recommendMusicList.add("Hotel california");
        recommendMusicList.add("Take Me Home Country Roads");
    }

    private void recommendMetal(List<String> recommendMusicList) {
        recommendMusicList.add("Don't cry");
        recommendMusicList.add("Fade to black");
    }
}

这样是不是很完美了!recommend 方法精简了很多,而且每种不同的推荐逻辑都被封装到相应的方法中了。那么,如果再加一种风格推荐怎么办?这有什么难,recommed 方法中加分支就好啦。然后在 RecommendMusicService 中增加一个对应的推荐方法。
等等,是不是哪里不太对?回想一下设计模式6大原则的开闭原则----对扩展开放,对修改关闭。面对新风格推荐的需求,我们一直都在修改 RecommendMusicService 这个类。以后每次有新风格推荐要添加,都会导致修改 RecommendMusicService 。显然这是个坏味道。

那么如何做到实现新的风格推荐需求时,满足开闭原则呢?

2. 音乐推荐器2.0版本

添加新需求时,如何做到不修改,去扩展?是不是想到了单一职责?是的,类的职责越单一,那么它就越稳定。RecommendMusicService 类的职责太多了,负责n种风格的推荐。OK,那么我们第一件事就是要减少 RecommendMusicService 类的职责,把每种不同风格的推荐提取到不同的类当中。
比如MetalMusicRecommendServicePopMusicRecommendServiceCountryMusicRecommendService。这些类都可以通过 recommed 方法生成推荐的歌曲清单。而 RecommendMusicService 类只是通过调用不同 MusicRecommendService 的 recommed 方法来实现推荐。代码如下:

MetalMusicRecommendService 类:

public class MetalMusicRecommendService {
    public List<String> recommend(){
        List<String> recommendMusicList = new ArrayList<>();

        recommendMusicList.add("Don't cry");
        recommendMusicList.add("Fade to black");

        return recommendMusicList;
    }
}

同类型的还有 GrungeMusicRecommendServicePopMusicRecommendServiceCountryMusicRecommendService

现在我们来改造 MusicRecommendService 类:

public class RecommendMusicService {

    private MetalMusicRecommendService metalMusicRecommendService = new MetalMusicRecommendService();
    private GrungeMusicRecommendService grungeMusicRecommendService = new GrungeMusicRecommendService();
    private CountryMusicRecommendService countryMusicRecommendService = new CountryMusicRecommendService();
    private PopMusicRecommendService popMusicRecommendService = new PopMusicRecommendService();

    public List<String> recommend(String style) {
        List<String> recommendMusicList = new ArrayList<>();

        if ("metal".equals(style)) {
            metalMusicRecommendService.recommend();
        } else if ("country".equals(style)) {
            countryMusicRecommendService.recommend();
        } else if ("grunge".equals(style)) {
            grungeMusicRecommendService.recommend();
        }else {
            popMusicRecommendService.recommend();
        }

        return recommendMusicList;
    }

}

改造后,如果有了新音乐风格推荐的需求,只需要增加相应的 xxxMusicRecommendService 类。然后在 RecommendMusicService 中增加相应分支即可。这样就做到了开闭原则。那么还有什么违背设计原则的地方吗?RecommendMusicService 是不是依赖的 xxMusicRecommendService 类太多了?

没错,而且这么多类,实际上都是做推荐的事情,且都是通过 recommend 方法提供推荐结果。这完全可以抽象出接口,比如 MusicRecommendInterface。那么 RecommendMusicService 依赖 MusicRecommendInterface 就可以了。这解决了依赖反转问题----应该依赖接口,而不是依赖具体实现

我们又复习了单一职责和依赖反转原则。不愧是指导设计模式的原则,真的是无处不在。依赖 MusicRecommendInterface 没问题,但是不同的音乐风格,怎么能实例化 MusicRecommendInterface 的某个具体实现呢?工厂模式于是就应运而生了!

3. 音乐推荐器3.0版本

我们回顾一下文章开头说到,工厂模式解决的是类的实例化。无论你需要哪种风格的 MusicRecommendService,只需要告诉工厂,工厂会给你实例化好你需要的具体实现。而工厂能做到这些是基于继承和多态。
RecommendMusicService 只需要依赖 MusicRecommendInterface,具体需要哪个MusicRecommendService 的实现,只需要告诉 RecommendServiceFactory 即可。MusicRecommendService 拿到具体的实现后调用它的 recommand 方法,就可以得到相应风格的推荐歌曲清单。

首先我们需要定义所有 MusicRecommendService 要实现的接口,很简单,只有一个 recommend 方法:

public interface MusicRecommendInterface {
    List<String> recommend();
}

我们2.0版本中的 xxxMusicRecommendService 都需要实现此接口,例如:

public class GrungeMusicRecommendService implements MusicRecommendInterface {
    public List<String> recommend() {
        List<String> recommendMusicList = new ArrayList<>();

        recommendMusicList.add("About a girl");
        recommendMusicList.add("Smells like teen spirit");

        return recommendMusicList;
    }
}

不同音乐风格的推荐逻辑在各自实现的 recommend() 方法中。
下面就是工厂模式中的工厂代码了,其实很简单,只是根据不同的参数实例化不同的实现并返回。

public class MusicRecommendServiceFactory {
    MusicRecommendInterface createMusicRecommend(String style) {
        if ("metal".equals(style)) {
            return new MetalMusicRecommendService();
        } else if ("country".equals(style)) {
            return new CountryMusicRecommendService();
        } else if ("grunge".equals(style)) {
            return new GrungeMusicRecommendService();
        } else {
            return new PopMusicRecommendService();
        }
    }
}

我们再来看看 RecommendMusicService 的代码:

public class RecommendMusicService {

    private MusicRecommendServiceFactory recommendMusicServiceFactory = new MusicRecommendServiceFactory();

    public List<String> recommend(String style) {

        MusicRecommendInterface musicRecommend = recommendMusicServiceFactory.createMusicRecommend(style);

        return musicRecommend.recommend();
    }
}

是不是简单多了,已经不再依赖那么多的 MusicRecommendInterface 的实现了。它要做的事情仅仅是通过工厂得到想要的 RecommendMusicService 实现,然后调用它的 recommend() 方法,就可以得到你想要的推荐结果。
类图如下:

 以上三种实现方式总结如下:

 4. 小结

本文我们通过音乐推荐器的例子,实践了如何找到程序中违反设计原则的地方,并通过工厂模式来解决这些问题。使用设计模式可以让程序更符合程序设计原则,从而写出更为健壮的代码。我们应牢记工厂模式解决的是类的实例化问题。这个例子很简单,不过涉及到的知识点却很多。有封装、多态、单一职责和依赖反转等。可见要想把程序设计好,必须熟练掌握这些基本概念和原则。


欢迎关注「慕课网」官方帐号,我们会一直坚持提供IT圈优质内容,分享干货知识,大家一起共同成长吧!

本文原创发布于慕课网 ,转载请注明出处,谢谢合作

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

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

相关文章

15.3:最多做K个项目,初始资金是W,返回最大资金

输入正数数组costs、正数数组profits、正数K和正数M costs[i]表示i号项目的花费 profits[i]表示i号项目在扣除花费之后还能挣到的钱(利润) K表示你只能串行的最多做k个项目 M表示你初始的资金 说明&#xff1a;每做完一个项目&#xff0c;马上获得的收益&#xff0c;可以支持你…

FP独立站支付渠道市场逐渐向好!信用卡和AB轮询哪个好?

之前一篇文章写过品牌方使用ChatGPT技术检测FP网站&#xff0c;对FP独立站的收款起到了很大的影响。今天是6月的第一天&#xff0c;我为各位带来了一个好消息&#xff01;那就是在过去的3-5月份&#xff0c;信用卡收款实行整顿&#xff0c;目前支付渠道都有所松动。例如&#x…

好孩子福利|Sup游戏机,一秒回到童年

这份六一礼物对儿童来说有点幼稚&#xff0c;但对程序员刚刚好&#xff5e; ​ Sup 游戏机&#xff0c;一秒回到童年&#xff01; 到底有多好玩呢&#xff1f;可以参考 B 站试玩视频&#xff01; 太火鸟好物推荐——掌上游戏机sup 参加流程&#xff1a; STEP 1&#xff1a;扫…

以太网——MDIO(SMI)接口的FPGA实现

在 MAC 与 PHY 之间&#xff0c;有一个配置接口&#xff0c;即 MDIO&#xff08;也称 SMI&#xff0c;Serial Management Interface&#xff09;&#xff0c;可以配置 PHY 的工作模式、获取 PHY 芯片的工作状态等。本文以 PHY 芯片 B50610 为例&#xff0c;实现 MDIO 接口&…

NUC972 Linux学习 NAND FLASH 制作系统

设备&#xff1a;NUC972DF61YC 使用的虚拟机环境&#xff1a;官方提供的NUC972DF61YC - Nuvoton 板载NAND FLASH&#xff0c;前期主要学习怎么uboot、ubootspl、uimage、env烧录。官方配置没有使用rootfs在flash中&#xff0c;所以数据会掉电丢失。即文件系统在RAM中。 这里仅…

基于Jackson实现API接口数据脱敏

一、背景 用户的一些敏感数据&#xff0c;例如手机号、邮箱、身份证等信息&#xff0c;在数据库以明文存储&#xff08;加密存储见《基于Mybatis-Plus拦截器实现MySQL数据加解密》&#xff09;&#xff0c; 但在接口返回数据给浏览器&#xff08;或三方客户端&#xff09;时&a…

设计一个支持并发的前端接口缓存

目录​​​​​​​ 缓存池 并发缓存 问题 思考 优化&#x1f914; 总结 最后 缓存池 缓存池不过就是一个map&#xff0c;存储接口数据的地方&#xff0c;将接口的路径和参数拼到一块作为key&#xff0c;数据作为value存起来罢了&#xff0c;这个咱谁都会。 const cach…

DTU和MQTT网关优缺点

目前市面上有两种设备实现Modbus转MQTT网关。网关式、DTU式。 钡铼技术网关内部进行转换 网关式 优点&#xff1a; 1、通讯模块和MCU分开&#xff0c;通讯模块只做通讯功能&#xff0c;协议转换有单独主控MCU&#xff0c;“硬转换”&#xff1b; 2、数据点是通过映射到主控…

【严重】GitLab 存在代码执行漏洞

漏洞描述 GitLab 是一款基于Git的代码托管、版本控制、协作开发平台。 GitLab CE/EE 15.4 至 15.9.6 版本&#xff0c;15.10 至 15.10.5 版本和 15.11 至 15.11.1 版本存在代码执行漏洞。在某些条件下&#xff0c;实例上的任何GitLab用户都可以使用GraphQL端点将恶意运行程序…

HTML框架-----标签(下)

目录 前言&#xff1a; 5.容器标签 效果&#xff1a;​编辑 6.列表标签 (1)无序 &#xff08;2&#xff09;有序 7.图片标签 8.超链接标签 &#xff08;1&#xff09;链接资源 &#xff08;2&#xff09;超链接锚点 前言&#xff1a; 今天我们接着来继续学习html的标签&am…

五重要性能测试指标揭秘!并发数、TPS、QPS、响应时间和资源利用率,了解性能瓶颈,优化系统高负载下的处理能力

目录 前言&#xff1a; 1. 并发数 2. TPS 3. QPS 4. 响应时间 5. 资源利用率 总结 前言&#xff1a; 在高并发的场景下&#xff0c;我们需要考虑如何优化我们的应用程序&#xff0c;以确保它可以承受大量的请求并且在给定时间内响应。对于这个问题&#xff0c;性能测试就…

字节码文件结构

目录 1、概述 2、JVM的两个无关性 3、Class字节码文件的结构 1、基本存储单位 2、字节码文件数据结构 3、Class文件格式 4、魔数与Class文件的版本 5、常量池 6、访问标志 7、类索引、父类索引与接口索引集合 8、字段表集合 9、方法表集合 10、属性表集合 11、总…

centos7.9升级rockylinux8.8

前言 查看centos的版本 &#xff0c;我这台服务器是虚拟机,下面都是模拟实验 升级前一定要把服务器上配置文件&#xff0c;数据等进行备份 [rootlocalhost ~]#cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core) [rootlocalhost ~]#uname -a Linux jenkins_ser…

Ubuntu常见基本问题

系列文章目录 文章目录 系列文章目录一、复制粘贴问题二、无法全屏问题三、设置为中文四、时间同步问题1、选择时区2、同步网络时间 一、复制粘贴问题 开启终端&#xff1a;ctrlaltt卸载已有工具 sudo apt-get autoremove open-vm-tools安装工具open-vm-tools sudo apt-get …

echarts的y轴数据显示过长占不下,内容截取,鼠标hover上去显示全部

初始效果&#xff1a; 优化后的效果&#xff1a; 优化点&#xff1a;控制了y轴显示字数&#xff0c;鼠标hover上去显示全部 主要实现思路参考了这位同学的文章&#xff1a;https://www.cnblogs.com/liuboren/p/9040622.html 我是用vue实现的&#xff0c;因为我需要一个页面中…

各算法/协议知识理论笔记(fpga)

一、利用fifo对3行数据求和 需要2个fifo保存第0行和第1行的数据&#xff0c;如下图 比如有20行数据&#xff0c;则将一行一行的输给fifo2, fifo2出来的数据再给fifo1.当fifo和fifo1有数据时&#xff0c;在准备给 fifo2输入新的一行数据时&#xff0c;同时读出fifo2&#xff0c;…

Linux进程间通信(信号)

信号发送 信号是 Linux 系统响应某些条件而产生的一个事件&#xff0c;接收到该信号的进程会执行相应的操作。 信号的产生有三种方式&#xff1a; &#xff08;1)由硬件产生&#xff0c;如从键盘输入 CtrlC 可以终止当前进程 &#xff08;2)由其他进程发送&#xff0c;如可在 …

PostgreSQL修炼之道之高可用性方案设计(十七)

目录 20 高可用性方案设计&#xff08;二&#xff09; 20.2 基于共享存储的高可用方案 20.2.1 SAN存储的方案 20.2.2 DRBD的方案 20.3 WAL日志同步或流复制同步的方案 20.3.1 持续复制归档的standby的方法 20.3.2 异步流复制的方案 20.3.3 基于同步流复制方案 20.4 基于…

国内外低代码开发平台发展情况如何?

国内外低代码开发平台发展情况如何&#xff1f;之前有些过很多关于低代码的内容&#xff0c;这篇就来详细梳理下国内外低代码开发平台发展现状。 关于低代码解读看这篇>>什么是低代码&#xff08;Low-Code&#xff09;&#xff1f;关于低代码平台看这篇>>主流的开…

业务高速增长,如祺出行如何用腾讯云消息队列 RocketMQ 应对挑战

导语 作为广汽集团旗下的智慧出行平台&#xff0c;如祺出行上线四年时间&#xff0c;用户规模和订单量保持高速增长。在过去的2022年&#xff0c;如祺出行平台累计注册用户突破1800万&#xff0c;同比增长64%&#xff0c;年度订单总量超7000万&#xff0c;同比增长52%。 高速…