本篇讲的是抽象工厂模式。
一、引出模式
场景:
我们在组装电脑时,通常会选择一系列的配件,比如CPU、硬盘、内存等。这里只讨论CPU和主板。
在选择CPU时,也会有它的品牌、型号、针脚数目等。
在选择主板时,也会有它的品牌、芯片组等。
在装机之前还有考虑这些配件之间的兼容性,也就是说,里面选择的各个配件之间是有关联的。
对于装机人来说,他只知道要组装电脑,需要相应的配件,但是具体用什么配件,还是客户说了算。
也就是说装机人只负责组装,而客户负责选择装机所需要的具体配件。
那我们使用程序来实现上面的过程吧!
对于客户,它需要选择自己需要的CPU和主板,然后告诉装机人自己的选择,接下来就等着装机人组装电脑。
对于装机人,它只知道要CPU和主板,而不知道具体需要什么类型的CPU和主板,我们可以将这定义成接口,很明显,我们可以用上简单工厂或工厂方法模式。
为简单演示,这里就是用简单工厂实现吧!
class Program { static void Main(string[] args) { Factory facroty = new Factory(); ICPU cpu = facroty.GetCpu(1); IMainboard mainboard = facroty.GetMainboard(1); cpu.Calculate(); mainboard.InstallCPU(); Console.ReadKey(); } } #region 简单工厂 public class Factory { /// <summary> /// 选择CPU /// </summary> /// <param name="cpuType"></param> /// <returns></returns> public ICPU GetCpu(int cpuType) { ICPU cpu = null; if (cpuType == 1) { cpu = new InterCpu(1000); } else if (cpuType == 2) { cpu = new AMDCpu(500); } else { cpu = new InterCpu(1000); } return cpu; } /// <summary> /// 选择主板 /// </summary> /// <returns></returns> public IMainboard GetMainboard(int mainboardType) { IMainboard mainboard = null; if (mainboardType == 1) { mainboard = new GAMMianborad(1000); } else if (mainboardType == 2) { mainboard = new MSIMianborad(500); } else { mainboard = new GAMMianborad(1000); } return mainboard; } } #endregion #region 产品 /// <summary> /// CPU接口 /// </summary> public interface ICPU { /// <summary> /// 运算功能 /// </summary> void Calculate(); } /// <summary> /// 主板接口 /// </summary> public interface IMainboard { /// <summary> /// 安装CPU /// </summary> void InstallCPU(); } /// <summary> /// InterCpu /// </summary> public class InterCpu : ICPU { /// <summary> /// 初始化CPU针脚 /// </summary> private int pins = 0; /// <summary> /// 构造函数初始化针脚数目 /// </summary> /// <param name="pins"></param> public InterCpu(int pins) { this.pins = pins; } public void Calculate() { Console.WriteLine("使用InterCpu,针脚数目是{0}", pins); } } /// <summary> /// AMDCPU /// </summary> public class AMDCpu : ICPU { /// <summary> /// 初始化CPU针脚 /// </summary> private int pins = 0; /// <summary> /// 构造函数初始化针脚数目 /// </summary> /// <param name="pins"></param> public AMDCpu(int pins) { this.pins = pins; } public void Calculate() { Console.WriteLine("使用AMDCpu,针脚数目是{0}", pins); } } /// <summary> /// 技嘉主板 /// </summary> public class GAMMianborad : IMainboard { /// <summary> /// CPU插槽孔数 /// </summary> private int cpuHoles = 0; /// <summary> /// 初始化CPU插槽孔数 /// </summary> /// <param name="cpuHoles"></param> public GAMMianborad(int cpuHoles) { this.cpuHoles = cpuHoles; } public void InstallCPU() { Console.WriteLine("使用技嘉主板,CPU插槽孔数是{0}", cpuHoles); } } /// <summary> /// 微星主板 /// </summary> public class MSIMianborad : IMainboard { /// <summary> /// CPU插槽孔数 /// </summary> private int cpuHoles = 0; /// <summary> /// 初始化CPU插槽孔数 /// </summary> /// <param name="cpuHoles"></param> public MSIMianborad(int cpuHoles) { this.cpuHoles = cpuHoles; } public void InstallCPU() { Console.WriteLine("使用微星主板,CPU插槽孔数是{0}", cpuHoles); } } #endregion
结果:
好了,看了上面的代码,在运行下,不是解决了这个问题了?
那我们把客户端代码稍微改一下,比如这样
class Program { static void Main(string[] args) { Factory facroty = new Factory(); ICPU cpu = facroty.GetCpu(1); //注意这里改变喽 IMainboard mainboard = facroty.GetMainboard(2); cpu.Calculate(); mainboard.InstallCPU(); Console.ReadKey(); } }
在看下结果:
好了,看出问题了吗?客户端选择的CPU针脚数是1000,而选择的主板的CPU插槽只有500针,压根就无法进行组装。
小结下,简单工厂解决了:对于装机人,只知道CPU和主板的接口,而不知道具体实现的问题,但还有一个问题没解决?
那就是这些CPU对象和主板对象其实是有关系的,是需要相互匹配的。而简单工厂,并没有维护这种关联关系。
二、认识模式
1.模式定义
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。(通过引出模式模式部分的讲解,相信大家对这句话有了足够的理解)
2.解决思路
仔细分析上面问题,其实有两个问题点:
一个是只知道所需要的一系列对象的接口,而不知道具体实现;
另一个是这一系列对象是相关或者相互依赖的,也就是说既要创建接口对象,还要约束它们之间的关系。
这里的问题与工厂方法或简单工厂解决的问题是有很大不同的,工厂方法或简单工厂关注的是单个产品对象的创建,比如创建CPU的工厂方法,他就只关心如何创建CPU对象。
这里要解决的是,要创建一系列的产品对象,而且这一系列对象是构建新的对象所需要的组成部分,也就是这一系列被创建的对象相互之间是有约束的。
解决这个问题的一个解决方案就是抽象工厂。
在这个模式中,会定义一个抽象工厂,在里面虚拟地创建客户端需要的这一系列对象,所谓虚拟的就是定义创建这些对象的抽象方法,并不去实现,然后由具体的抽象工厂子类来提供这一系列对象的创建。
这样就可以为同一个抽象工厂提供很多不同的实现,那么创建的这一系列对象也就不同了。也就是说,抽象工厂在这里起一个约束作用,并提供所有子类的一个统一外观,来让客户端使用。
3.模式结构(请使用鼠标右键——在新标签页打开链接)
AbstractFactory:抽象工厂,定义创建一系列产品对象的接口,通俗点就是定义生产线,在本例中约束了装机方案需要的标准配件。
Scheme1:具体的工厂,实现抽象工厂定义的方法,具体实现一系列产品对象的创建。在本例中是有AMDCPU和GAMainboard主板组成的装机方案。
ICPU:定义一类产品的接口,在本例中就是定义CPU的接口。
InterCPU:具体的产品实现的对象,通常在具体工厂里面,会选择具体的产品实现对象,来创建符合抽象工厂定义的方法返回的产品类型。
ComputerEngineer:在这里充当装机人的角色,具体的装机工作交给他,其实就是对客户端的进一步封装,使的客户端代码用起来更少,更方便。
4.示例代码
class Program { static void Main(string[] args) { //创建装机工程师 ComputerEngineer enginner = new ComputerEngineer(); //客户选择装机方案 AbstractFactory schema = new Schema1(); //开始组装电脑 enginner.MakeComputer(schema); Console.ReadKey(); } } #region 组装人 /// <summary> /// 组装人 /// </summary> public class ComputerEngineer { private ICPU cpu = null; private IMailboard mailboard = null; public void MakeComputer(AbstractFactory schema) { //1.准备装机所需配件 this.PrepareHardwares(schema); //2.组装电脑 //3.测试电脑 //4.交付客户 } /// <summary> /// 准备装机所需配件 /// </summary> /// <param name="schema"></param> private void PrepareHardwares(AbstractFactory schema) { this.cpu = schema.CreateCPU(); this.mailboard = schema.CreateMailboard(); this.cpu.Calculate(); this.mailboard.InstallCpu(); } } #endregion #region CPU产品 /// <summary> /// CPU /// </summary> public interface ICPU { void Calculate(); } public class InterCPU : ICPU { private int pins = 0; public InterCPU(int pins) { this.pins = pins; } public void Calculate() { Console.WriteLine("IntelCPU针脚有" + this.pins); } } public class AMDCPU : ICPU { private int pins = 0; public AMDCPU(int pins) { this.pins = pins; } public void Calculate() { Console.WriteLine("AMDCPU针脚有" + this.pins); } } #endregion #region 主板产品 public interface IMailboard { void InstallCpu(); } public class MSIMailboard : IMailboard { private int cpuHoles = 0; public MSIMailboard(int cpuHoles) { this.cpuHoles = cpuHoles; } public void InstallCpu() { Console.WriteLine("MSI主板针座" + this.cpuHoles); } } public class GAMailboard : IMailboard { private int cpuHoles = 0; public GAMailboard(int cpuHoles) { this.cpuHoles = cpuHoles; } public void InstallCpu() { Console.WriteLine("GA主板针座" + this.cpuHoles); } } #endregion #region 工厂 /// <summary> /// 抽象工厂 /// </summary> public interface AbstractFactory { ICPU CreateCPU(); IMailboard CreateMailboard(); } public class Schema1 : AbstractFactory { public ICPU CreateCPU() { return new AMDCPU(2048); } public IMailboard CreateMailboard() { return new GAMailboard(2048); } } public class Schema2 : AbstractFactory { public ICPU CreateCPU() { return new InterCPU(1024); } public IMailboard CreateMailboard() { return new MSIMailboard(1024); } } #endregion
三、理解模式
1.抽象工厂的功能
抽象工厂的功能是为一系列相关对象或相互依赖的对象创建一个接口,注意这个接口内的方法不是随意定义的,而是一系列相关或相互依赖的方法。
抽象工厂其实是一个产品系列,也就是产品簇。上面例子中的抽象工厂就可以看成电脑簇,每个不同的装机方案,就是一种具体的电脑系列。
2.实现成接口
AbstrctFactory通常会实现成为接口,当然,如果需要为这个产品簇提供公共的功能,也可以将AbstrctFactory实现成抽象类,但一般不会这么做。
3.使用工厂方法
AbstrctFactory定义了创建产品所需要的接口,具体的实现是在具体实现类里面,在实现类里面就需要选择多种更具体的实现。所以AbstrctFactory定义的创建产品的方法看成是工厂方法,也就是说使用工厂方法来实现抽象工厂。
4.模式优点
1)分离接口和实现
客户端使用抽象工厂来创建需要的对象,而客户端根本就不知道具体的实现是谁,客户端知只是面对产品接口编程而已。就是说,客户端从具体的产品实现中解耦了。
2)使得切换产品簇变得容易
因为一个具体的工厂实现代表一个产品簇,比如例子中的Scheme1和Scheme2,客户端选用不同的工厂实现,就相当于是在切换不同的产品簇。
5.模式缺点
1)不要容易扩展新的产品
如果需要给整个产品簇添加一个新的产品,那么就要修改抽象工厂,这样就会导致修改所有的工厂实现类。
2)容易造成类层次复杂,增加代码的复杂性。
6.模式的选用
1)如果希望一个系统独立于它的产品创建、组合和表示时。就是说,希望一个系统只知道产品的接口,而不关心实现的时候。
2)如果一个系统有多个产品系列中的一个来配置时。换句话说,就是可以动态切换产品簇时。
3)如果要强调一系列相关产品的接口,以便联合使用它们的时候。
7.模式的总结
抽象工厂的本质是选择产品簇的实现。
工厂方法是选择单个产品的实现,虽然一个类里面可以有多个工厂方法,当时这些方法之间一般是没有联系的,即使看起来有联系。
但是抽象工厂着重的就是为一个产品簇选择实现,定义在抽象工厂里面的方法通常是有联系的,它们都是产品的某一部分或者是相互依赖的。
下一篇,准备是来学习下三种工厂模式之间的区别和联系,以及一些扩展。