笑说设计模式-小白逃课被点名

简介

工厂模式(Factory Pattern)是最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,而是通过使用一个共同的接口来指向新创建的对象。

分类

工厂模式可以分为三种,其中简单工厂一般不被认为是一种设计模式,可以将其看成是工厂方法的一种特殊。

  • 简单工厂

  • 工厂方法

  • 抽象工厂

场景分析

平凡枯燥的文字总是让人看得想睡觉,接下来我们用几个情景案例来进行分析

简单工厂

直接通过一个Factory【工厂类】类创建多个实体类的构造方式。

时间:2021年2月19日 地点:教室 人物:学生小白、老师、大佬黑皮


小白是一名大二的计算机系学生,懒惰不爱学习。今天早晨第一节课就因为睡懒觉迟到被老师逮个正着,这节课还正好是小白最头疼的上机课"C#设计模式”。这不,课堂上到一半老师就开始提问,小白“光荣”的成为了老师的点名对象。

老师笑着说道:“小白,请你解答一下屏幕上的问题。”

题目:请使用c#、java、python、php或其他任一面向对象编程语言实现输入俩个合法数字和一个合法符号,输出对应结果的功能。

小白一看,这算什么题目,这么简单,看我不手到擒来,伴随着双手噼里啪啦一顿敲击声音,屏幕上出现一串编码。

**Calculator操作类 **

    public class Calculator{public double GetResult(double A, double B, string operate){double result = 0d;switch (operate){case "+": result = A + B;break;case "-":result = A - B;break;case "*":result = A * B;break;case "/":result = A / B;break;default: break;}return result;}}

客户端代码

    class Program{static void Main(string[] args){Console.WriteLine("请输入数字A");string a = Console.ReadLine();Console.WriteLine("请输入数字B");string b = Console.ReadLine();Console.WriteLine("请选择操作符号(+、-、*、/)");string operate = Console.ReadLine();Calculator box = new Calculator();double result = box.GetResult(Convert.ToDouble(a), Convert.ToDouble(b), operate);Console.WriteLine(result);Console.ReadKey();}}

小白:”老师,我写好了“

老师:"不错,写的很好。使用到了面向对象三大特性中的封装,将计算方法封装成了一个计算类,多个客户端可以复用这个类。但是这其中也有不少的问题,哪位同学来回答一下。"

黑皮:”小白同学写的代码有俩处问题。

1、如果输入A=10,B=0,程序就会报错,没有做输入的有效性验证

2、如果操作符号不按照规定的输入 ,也会导致报错“

老师:”黑皮同学回答的非常好,但是这都只是针对代码业务逻辑错误的描述。有没有谁可以看出更加深层次的内容。“

黑皮:”老师,你给点提示吧。“

老师:”这个可以会有点隐蔽,老师就给出一点提示。如果我们增加一个新的运算怎么办?“

小白立即回答到:”在switch中增加一个新的分支就可以了“。

老师:”这样当然是没有错误的,但是问题在于,我只是增加了一个新的运算符号,却需要让加减乘除所有的运算都参加编译,如果在修改的过程中不小心修改了其他的代码,例如把+号改成了-号,那不是很糟糕,这就违背了开闭原则【对扩展开放,对修改关闭】“

小白挠一挠头问道:”开闭原则,这是什么?“

老师:”这就是你不认真听课落下的内容,回去好好补习。黑皮同学,不知道你Get到了老师的点没有?“

黑皮:”我知道应该如何修改了。小白实现了面向对象三大特性之一的封装,其实就是将其他俩个特性一起使用就可以完成老师要的功能“

小白:”多态和继承?“

黑皮:”是的,等我改完你再看程序就应该有感觉了“

    public class Operate{public double NumberA { get; set; }public double NumberB { get; set; }public virtual double GetResult(){return 0;}}public class OperateAdd : Operate{public override double GetResult(){return this.NumberA +this.NumberB;}}public class OperateSub : Operate{public override double GetResult(){return this.NumberA - this.NumberB;}}

简单工厂

    public class OperateFactory{public static Operate GetOperateFactory(string operate){Operate fac = null;switch (operate){case "+":fac = new OperateAdd();break;case "-":fac = new OperateSub();break;case "*":fac = new OperateMul();break;case "/":fac = new OperateDiv();break;default:break;}return fac;}}

黑皮:”首先是一个运算类,里面有俩个Number属性和一个虚方法GetResult()。加减乘除四个方法作为运算类的子类继承,继承后重写GetResult()方法,调用基类的A和B公有属性进行不同的数学运算。“

黑皮:”然后定义一个简单工厂,静态方法传入操作符参数得到实际的业务处理类,客户端得到处理类后对参数赋值。最后一步你应该知道怎么做了吧“

小白:”我懂了,那客户端这么调用就可以了“。

  static void Main(string[] args){Console.WriteLine("请选择操作符号(+、-、*、/)");string operateStr = Console.ReadLine();Operate operate = OperateFactory.GetOperateFactory(operateStr);operate.NumberA = 10;operate.NumberB = 4;double result = operate.GetResult();Console.WriteLine(result);Console.ReadKey();}

老师:”看来俩位同学都已经掌握了简单工厂的使用,接下来我提问几个问题,便于大家更快的掌握这种设计模式“

老师:”如果我们要修改除方法的逻辑,增加被除数为0的逻辑应该怎么做“

小白:”直接修改OperateDiv类,这不会对其他造成影响“

老师:”如果我们要新增一个开根运算应该怎么做“

小白:”添加一个新的类,Operate开根类,里面描述开根的逻辑。在工厂方法中将新的操作符添加进去即可。新增的操作单独一个类,也不会对其他方法体造成影响“。

小白:”那客户端需要做什么改变吗?“

老师:”客户端要做什么改变,客户端只要处理好自己的事情就可以了!“

是的,客户端不关心工厂创建了什么,工厂是一个黑盒子。客户端只要传入参数,工厂负责将内容生产后【实例化类的过程】给客户端即可。

优/缺点

简单工厂模式的工厂类一般是使用静态方法,通过接收的参数不同来返回不同的对象实例。不修改代码的话,是无法扩展的 优点:客户端可以免除直接创建产品对象的责任,而仅仅是“消费”产品。简单工厂模式通过这种做法实现了对责任的分割 缺点:由于工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了

工厂方法

时间:2021年2月19日下午 地点:教室 人物:学生小白、老师、大佬黑皮

老师:”我们紧接着上午的设计模式继续,上午我们讲的是简单工厂,下午我们讲下一个内容工厂方法。工厂方法和简单工厂其实大同小异,唯一的区别就在于每一个实现抽象类的实例(也叫做产品,即上午定义的加减乘除四个子类)都有一个对应的工厂去创建。同学们了解一下老师说话的内容,然后寻找一个场景编码实现一下。最快完成的有课堂奖励”

....几分钟过去了.....

小白:“老师,我完成了。”

老师:“好的,那我们请小白同学说明一下场景和实现的方式。”

我设计的是以水果作为场景的模式。

1、定义一个抽象类Fruit.cs,这个类定义俩个抽象方法printfColor()printfName()

2、实现俩种不同的水果分别继承此抽象类并复写抽象方法。

3、定义一个工厂接口,定义接口方法createFruit()

4、实现俩个不同的工厂分别实现水果实例的创建。

水果抽象类

    public abstract class Fruit{public abstract void PrintfColor();public abstract void PrintfName();}public class Apple : Fruit{public override void PrintfColor(){Console.WriteLine("红色");}public override void PrintfName(){Console.WriteLine("苹果");}}

工厂接口

   public interface IFruitFactory{Fruit CreateFruit();}public class AppleFactory : IFruitFactory{public Fruit CreateFruit(){return new Apple();}}

客户端实现

           //苹果工厂IFruitFactory appleFac = new AppleFactory();Fruit apple = appleFac.CreateFruit();apple.PrintfColor();apple.PrintfName//橘子工厂IFruitFactory orangeFac = new OrangeFactory();Fruit orage = orangeFac.CreateFruit();orage.PrintfColor();orage.PrintfName();

老师:“看来小白同学已经对上午的内容有了一个充分的了解,果然好好上课才能够学习到新的知识。逃课是没有益处的”

老师:“只是这样的案例太过简单,可能其他同学不是很能理解为什么要这样 ,我来举一个实际场景的案例方便大家理解。在实际的工作过程中我们总会用到日志组件,例如Nlog,Log4net这种第三方组件,这种组件都支持可配置化的多源输出。当我们在配置文件(json/xml)中增加一个“输出到控制台的参数”,程序 就会将内容输出到控制台,当配置一个输入到文件的参数,程序就会将内容输出到指定的文件。这种场景的实现其实就是一种典型的工厂方法。下面我来分析一下过程”

1、读取配置文件(json/xml)

2、获取所有的配置方式,循环遍历

3、判断配置类型,如果是输入到文件的配置。new一个文件日志工厂,将配置信息作为参数传递,便于后期方法调用;如果是输入到控制台的配置。new一个日志工厂也是做同样的操作

4、每一个工厂只管理自己的事情,但是应该都拥有日志输出这个接口。

5、当上层调用打印方法时候,循环遍历所有的工厂,调用接口的日志输出输出方法

优/缺点

工厂方法是针对每一种产品提供一个工厂类。通过不同的工厂实例来创建不同的产品实例。在同一等级结构中,支持增加任意产品 优点:允许系统在不修改具体工厂角色的情况下引进新产品 缺点:由于每加一个产品,就需要加一个产品工厂的类,增加了额外的开发量

抽象工厂

抽象工厂模式为创建一组对象提供了一种解决方案。与工厂方法模式相比,抽象工厂模式中的具体工厂不只是创建一种产品,它负责创建一族产品。

时间:2021年2月20日上午 地点:教室 人物:学生小白、老师、黑皮

新的一天又开始了,“设计模式”课程在小白的眼中好像没有那么复杂了,今天小白早早地就到了教室,准备迎接老师新的鞭策。

老师:”同学们早上好,今天我们继续昨日的课程。昨天讲的是工厂方法,今天我们在此基础上做一点改进,看看又有什么新的变化。小白同学学习热情很高嘛,现在都知道坐在第一排了。不错不错,值得鼓励”

小白:”嘻嘻“

老师:“好,那开始今天的课程。今天要讲的模式是抽象工厂模式。通过和工厂模式做比较,同学们可以比较清晰的发现这俩都之间的区别。我们用昨天小白同学的例子继续开拓。”

此时有苹果和橘子俩个产品,分别对应苹果工厂和橘子工厂。这是工厂方法的体现。可是如果有3个不同的工厂,他们分别都生产苹果和橘子呢。

小白:“恩...那就多建立几个工厂。每个产品分别对应不同的工厂,应该是这样的一个结构,每一个工厂分别对应生产产品的类”

A

  • A_苹果工厂.cs

  • A_橘子工厂.cs

B

  • B_苹果工厂.cs

  • B_橘子工厂.cs

C

  • C_苹果工厂.cs

  • C_橘子工厂.cs

老师:“这样的方式当然是可以的,可以如果我有10个工厂呢,难道我们要建立10*2=20个类吗,这样程序的复杂度就是直线上升,不利于维护。”

小白:“那怎么办呢,用老师你说的那种抽象工厂吗?如果用,又应该怎么做呢”

老师:“是的,在这样的场景下,抽象工厂是最能匹配的设计模式。其实做法非常简单,对昨天的代码进行一些修改即可”

水果抽象类

新增一个Name属性,方便后期打印不同的工厂。

    public abstract class Fruit{public string Name { get; set; }public abstract void PrintfColor();public abstract void PrintfName();}public class Apple : Fruit{public Apple(string name){this.Name = name;}public override void PrintfColor(){Console.WriteLine(this.Name + "红色");}public override void PrintfName(){Console.WriteLine(this.Name + "苹果");}}

工厂接口

老师:“这一处的改动就比较明显。原来的接口中方法输出唯一的产品——因为之前每一个工厂只生产一件产品。现在输出俩个产品,即继承工厂接口的类必须实现生产苹果和橘子的方法。这样的好处在于,每一个工厂负责管理自己产品的实现,避免了每一个产品都需要创建一个工厂的操作。“

解释:

工厂生产苹果和橘子。当有多个工厂的时候,每一个工厂都实现生产苹果和橘子。而不是生产A厂苹果需要一个工厂实现类,生产B厂苹果又需要一个。如下所示

旧模式

A

  • A_苹果工厂.

  • A_橘子工厂

B

  • B_苹果工厂

  • B_橘子工厂

C

  • C_苹果工厂

新模式

A 工厂

  • 苹果/橘子

B 工厂

  • 苹果/橘子

C 工厂

  • 苹果/橘子

老师:“这样复杂度由原来的6变成了3。”

小白:"我明白了,又学习到了新的东西。"

    public interface IFruitFactory{Fruit CreateApple(string name);Fruit CreateOrange(string name);}public class AFactory : IFruitFactory{public Fruit CreateApple(string name){return new Apple(name);}public Fruit CreateOrange(string name){return new Orange(name);}}

客户端实现

            IFruitFactory fac = new AFactory();Fruit a_Apple = fac.CreateApple("a工厂");Fruit a_Orange = fac.CreateOrange("a工厂");a_Apple.PrintfName();a_Orange.PrintfName();IFruitFactory b_fac = new BFactory();Fruit b_Apple = b_fac.CreateApple("b工厂");Fruit b_Orange = b_fac.CreateOrange("b工厂");b_Apple.PrintfName();b_Orange.PrintfName();

小白:“可是在什么样的场景下用这种模式呢,我好像一下子想不到”

老师:“抽象工厂的使用相对来说比较少,但也不是没有。我举一个例子,在后端开始中我们有各种的组件【按钮,抽屉,导航栏】等等,这些组件有对应的皮肤,对皮肤的开发就是抽象工厂的实现。工厂接口是对每个组件的定义,每个皮肤就是一个工厂的实现。如果要切换皮肤,只需要实例化不同的工厂即可。”

小白:“哦。就和游戏中的皮肤切换一样吗?”

老师:“你也可以这样理解,设计模式只是一种通用解决方案,可以应用在不同的场景下,大家可以挑最适应自己,最好理解的场景下手。”

下课铃声又响起了

老师:“好了,这节课就到这里。下节课我们讲其他的设计模式,希望大家准时听讲。”

优/缺点

抽象工厂是应对产品族概念的。应对产品族概念而生,增加新的产品线很容易,但是无法增加新的产品。比如,每个汽车公司可能要同时生产轿车、货车、客车,那么每一个工厂都要有创建轿车、货车和客车的方法 优点:向客户端提供一个接口,使得客户端在不必指定产品具体类型的情况下,创建多个产品族中的产品对象 缺点:增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,对“开闭原则”的支持呈现倾斜性

推荐阅读

Redis工具收费后新的开源已出现

GitHub上Star最高的工程师技能图谱

中国程序员最容易发错的单词

推荐!!! Markdown图标索引网站

END

欢迎关注公众号 程序员工具集 ????????  致力于分享优秀的开源项目、学习资源 、常用工具

回复关键词“关注礼包”,送你一份最全的程序员技能图谱。

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

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

相关文章

原来论文排版还有这样的学问

论文的重要加分点除了内容,还有它小天最近经常遇到小伙伴的诉苦:“我的毕业论文提交一次就被导师批评一次,内容不行就算了,格式也有问题!改论文改到绝望”“期刊的版式要求不是统一的,为了多投几家&#xf…

红帽linux5安装Oracle 9i enterprise

红帽linux5安装Oracle 9i enterprise 本文转自:http://database.51cto.com/art/201004/194082.htm摘要:如果你在红帽Linux5上安装Oracle 9i enterprise遇到了问题,不防看一看下面的文章,希望能帮你解决有关Oracle9i实际应用的问题…

powerpc-linux-gcc,关于powerpc-linux-uclibc-gcc的使用

做了点东西需要使用linux下ldap库,我装的linux上已经有ldap库,在程序中#include 了。用gcc编译时:gcc filename.c,会报找不到ldap.h文件以及ldap.h文件中相关函数的错误但是使用gcc filename.c -lldap,就能够编译通过了。后来要求…

神经网络告诉我,谁是世界上最「美」的人?

「魔镜魔镜告诉我,谁是世界上最美的女人?」这句伴随童年的话也有现实版哦~神经网络可以预测人脸颜值,这方面也出现了不少研究。今年年初华南理工大学的研究者发布论文,并公开了数据集 SCUT-FBP5500。本文作者 Dima Shu…

开源推荐:.Net Core3.1 + EF Core + LayUI 封装的MVC版后台管理系统

ASP.NET Core是一个由微软创建的,用于构建Web应用程序,API,微服务的Web框架。它使用常见的模式,例如MVC(模型-视图-控制器),依赖注入,和一个由中间件构成它基于Apache 2.0许可开放源…

android中文api(85)——HorizontalScrollView

前言 本章内容是android.widget.HorizontalScrollView,译为"横向滚动条",版本为Android 2.3 r1,翻译来自"Tina",感谢"Tina"为大家带来精彩的翻译稿 !期待你加入Android API 中文的翻译&…

linux 连接wifi wpa2,RHEL等Linux系统使用wpa_supplicant以WPA-PSK/WPA2-PSK连接WIFI

以RHEL6.4为实验对象:1、安装 wpa_supplicant.yum install wpa_supplicant2、编辑 wpa_supplicant 配置文件vim /etc/sysconfig/wpa_supplicant以下格式:# Use the flag "-i" before each of your interfaces, like so:# INTERFACES"-ie…

如何在 C# 中使用只读的 Collections

集合 表示一组可用于获取和存储的对象,在 C# 中提供了两种类型的集合。普通集合泛型集合前者存在于 System.Collections 命名空间下,属类型不安全的,后者存在于 System.Collections.Generic 命名空间下,属类型安全的。不可变对象 …

操作系统和数据库的知识梳理(思维导图)

使用思维导图工具 freemind 提供下载 源码文件和 Java网页版 http://files.cnblogs.com/facingwaller/osanddb.rar

令人难以理解的软件工程师:几千行代码能搞定的为什么要写几万行?

一我们公司的 Windows 版软体已经有十多年的历史,经过历代工程师的整治之后,内容已经凌乱不堪。过去三个月,我找时间自己重写了整个主程序。原本数万行的程序,被我重写的只剩下数千行,功能不变,效能更好&am…

如何从 dump 文件中提取出 C# 源代码?

一:背景 相信有很多朋友在遇到应用程序各种奇葩问题后,拿下来一个dump文件,辛辛苦苦分析了大半天,终于在某一个线程的调用栈上找到了一个可疑的方法,但 windbg 常常是以 汇编 的方式显示方法代码的,可惜的是…

通信开源linux,Linux环境进程间通信

Linux环境进程间通信(一)http://www.ibm.com/developerworks/cn/linux/l-ipc/part1/index.htmlLinux环境进程间通信(二): 信号(上)http://www.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.htmlLinux环境进程间通信(二): 信号(下)http://www.ibm.com/developerworks/cn…

真人拳皇项目Alpha阶段的回顾——史经浩

经过一个多月艰辛的努力,我们小组终于在1/14按时发布了真人拳皇的alpha版。按照计划,这一周是总结过去这段时间的经验教训,为即将到来的beta阶段作准备。回顾刚刚过去的一个多月,从plan阶段的天马行空,到后来的逐渐降级…

在栈中压入一个字符串c语言,面试题 31:栈的压入、弹出序列

题目描述输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,…

谈谈对IOC及DI的理解与思考

一、前言在实际的开发过程中,我们经常会遇到这样的情况,在进行调试分析问题的时候,经常需要记录日志信息,这时可以采用输出到控制台。因此,我们通常会定义一个日志类,来实现输出日志。定义一个生成验证的逻…

关于perl和shell的参数传递

Perl 1.命令行参数传递 *Perl **ARGV $_ARGV[0] $#ARGV为参数的个数-1 ($#array 只适用于array不适用%hash) *Shell $1 $2 参数个数$#(不用减1,是否可以用来判断是否有命令行参数?) 2.函数参数传递 *Perl **_ 传递了所有的函数参数。如果是对象中的方法。$_[0] 是…

NET问答:如何理解 IEnumerableT 和 IQueryableT

咨询区 stackoverflowuser&#xff1a;请问类型 IQueryable<T> 和 IEnumerable<T> 有什么异同&#xff1f;我应该使用哪一个而不是另一个&#xff0c;代码如下&#xff1a;IQueryable<Customer> custs from c in db.Customers where c.City "<Cit…

c语言实现字母转化为unicode码,用C语言实现中文到unicode码的转换

由于本人喜欢用Notepad编辑器&#xff0c;该编辑器的好处是小巧灵活&#xff0c;但是有几个地方做的不足&#xff0c;但是我都能够很好的采取相应的措施来替代&#xff0c;下面让我们看看Notepad有哪些地方的不足&#xff0c;进而采取怎样的措施。一&#xff1a;Notepad不能编译…

终于有人把云计算、大数据和人工智能讲明白了!

今天跟大家讲讲云计算、大数据和人工智能。为什么讲这三个东西呢&#xff1f;因为这三个东西现在非常火&#xff0c;并且它们之间好像互相有关系&#xff1a;一般谈云计算的时候会提到大数据、谈人工智能的时候会提大数据、谈人工智能的时候会提云计算……感觉三者之间相辅相成…

c语言程序设计新编教程答案钱雪忠,新编C语言程序设计教程

图书简介配套资源&#xff1a;电子课件、习题解答、源代码本书特色&#xff1a;★精选例题&#xff0c;引入了大量趣味性、游戏性应用实例&#xff0c;注重与加强程序阅读、参考、编写和上机调试实践的能力&#xff0c;重在编程思路的培养与训练。★概念清晰但不烦琐&#xff1…