共计 7318 个字符,预计需要花费 19 分钟才能阅读完成。
简介
工厂模式(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();
小白:“可是在什么样的场景下用这种模式呢,我如同一下子想不到”
老师:“形象工厂的应用相对来说比拟少,但也不是没有。我举一个例子,在后端开始中咱们有各种的组件【按钮,抽屉,导航栏】等等,这些组件有对应的皮肤,对皮肤的开发就是形象工厂的实现。工厂接口是对每个组件的定义,每个皮肤就是一个工厂的实现。如果要切换皮肤,只须要实例化不同的工厂即可。”
小白:“哦。就和游戏中的皮肤切换一样吗?”
老师:“你也能够这样了解,设计模式只是一种通用解决方案,能够利用在不同的场景下,大家能够挑最适应本人,最好了解的场景下手。”
下课铃声又响起了
老师:“好了,这节课就到这里。下节课咱们讲其余的设计模式,心愿大家准时听讲。”
优 / 毛病
形象工厂是应答产品族概念的。应答产品族概念而生,减少新的产品线很容易,然而无奈减少新的产品。比方,每个汽车公司可能要同时生产轿车、货车、客车,那么每一个工厂都要有创立轿车、货车和客车的办法
长处:向客户端提供一个接口,使得客户端在不用指定产品具体类型的状况下,创立多个产品族中的产品对象
毛病:减少新的产品等级构造很简单,须要批改形象工厂和所有的具体工厂类,对“开闭准则”的反对出现歪斜性