今天跟大家分享一下我对工厂系列模式的理解,主要包括简单工厂、工厂方法、抽象工厂模式。
场景一:
话说苹果和小米是两大手机巨头,他们各自推出的经典手机是IPhone手机系列和小米手机系列。
两家公司在手机的整个开发过程当中,都只负责产品的研发和设计工作,而手机制造则委托给手机工厂。
设计模式中有一个叫面向抽象编程的原则,在程序中体现为使用接口或者抽象类。下面我们将苹果和小米手机的共同特征和
行为来做一个抽象,这里我们使用抽象类,如下:
MobilePhone.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DPC.Domain { public abstract class MobilePhone { public MobilePhone(string model,decimal price) { this.Model = model; this.Price = price; } /// <summary> /// 名字 /// </summary> public abstract string Name { get; } /// <summary> /// 型号 /// </summary> public string Model { get; private set; } /// <summary> /// 价格 /// </summary> public decimal Price { get; private set; } /// <summary> /// 综合情况展示 /// </summary> public virtual void Show() { Console.WriteLine("品牌:{0},型号:{1},价格:{2}", this.Name, this.Model, this.Price); } } }
由于手机都有名字、型号、价格等等一些特征,所以我们可以将这些共有的行为和特征抽象到基类当中,避免子类中出现不必要的冗余代码。
接下来我们就用IPhone和XiaoMi手机两个类继承基类,在基类中实现各自独有的行为和特性,代码如下:
IPhone.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DPC.Domain { public class IPhone : MobilePhone { private static readonly string name = "苹果"; public IPhone(string model, decimal price) : base(model, price) { } public override string Name { get { return name; } } public override void Show() { Console.WriteLine("土豪手机闪亮登场......"); Console.WriteLine("品牌:{0},型号:{1},价格:{2}",this.Name,this.Model,this.Price); } } }
XiaoMi.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DPC.Domain { public class XiaoMi : MobilePhone { public XiaoMi(string model, decimal price) : base(model, price) { } public override string Name { get { return "小米"; } } public override void Show() { Console.WriteLine("made in china手机闪亮登场......"); Console.WriteLine("品牌:{0},型号:{1},价格:{2}", this.Name, this.Model, this.Price); } } }
上面的代码中我们初步定义好了手机基类和实现类,并且用到了面向抽象编程的原则,为的是以后扩展程序更简单。
到现在为止,如果我们将上面的代码编译为DLL提供给第三方调用,那么调用的场景将会是这样子滴:
MobilePhone phone = new IPhone("苹果", 5000); phone.Show(); MobilePhone xiaomi = new XiaoMi("小米", 2000); xiaomi.Show();
调用者需要知道MobilePhone 这个类以及IPhone和XiaoMi两个子类,如果有别的品牌手机那么也需要知道相关子类。
如果当我们的手机品牌足够多,那么这个时候调用者调用我们的代码时将会无所适从。调用者不关心手机是如何生产的,
难道调用不能简化吗??
简化调用第一招,简单工厂(也称静态工厂)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using DPC.Domain; namespace DPC.CreateModel.SimpleFactory { class Program { static void Main(string[] args) { MobilePhone phone = MobilePhoneFactory.GetMobilePhone("苹果",5000); phone.Show(); Console.WriteLine(); MobilePhone xiaomo = MobilePhoneFactory.GetMobilePhone("小米",2000); xiaomo.Show(); Console.ReadKey(); } } //简单工厂(又名静态工厂) public class MobilePhoneFactory { public static MobilePhone GetMobilePhone(string name,decimal price) { switch(name) { case "苹果": return new IPhone("iphone6", price); case "小米": return new XiaoMi("小米青春版", price); default: throw new NotImplementedException("不存在的品牌"); } } } }
总结:这下不管有多少种不同的手机品牌,调用者只要告诉工厂需要的手机即可,根本不用去管具体是哪个手机子类被实例化了。
优点:该模式代码简单易于理解,学习成本较小。适用于场景单一、变动相对较小、对可扩展和可维护性要求不高的代码。
缺点:业务变动时需要修改工厂中的已有代码,违背了对修改关闭、对扩展开放的设计原则,不利于程序的扩展和维护。
场景二:
通过对简单工厂模式的使用,我们简化了外部调用代码的难度。但是,当我们增加一种产品时简单工厂里面的分支语句也得做相应的调整,
这显然违背了开闭的设计原则。针对简单工厂模式的这种缺陷,工厂方法模式提出了自己的解决方案。工厂方法
首先从单一职责原则入手,改进了简单工厂身兼数职的缺陷,一个具体的工厂方法只生产一种产品。其次从依赖倒置和面向抽象编程的原则入手,抽象
和提取出公共的工厂方法接口。
/// <summary> /// 手机工厂接口(只要实现了该接口的类就是具备生产某种手机能力的工厂,这里有可能是苹果手机或者小米手机工厂) /// </summary> public interface IMobilePhoneFactory { MobilePhone CreateMobilePhone(); } //IPhone手机工厂 public class IPhoneFactory : IMobilePhoneFactory { public MobilePhone CreateMobilePhone() { return new IPhone("iphone6 plus", 6500); } } //小米手机工厂 public class XiaoMiFactory : IMobilePhoneFactory { public MobilePhone CreateMobilePhone() { return new XiaoMi("小米2代", 2500); } }
工厂方法调用代码:
class Program { static void Main(string[] args) { //创建生产小米手机的工厂 IMobilePhoneFactory xiaomiFactory = new XiaoMiFactory(); MobilePhone xiaomi = xiaomiFactory.CreateMobilePhone(); //调用工厂对象生产手机的方法 xiaomi.Show(); Console.WriteLine(); //创建生产苹果手机的工厂 IMobilePhoneFactory iphoneFactory = new IPhoneFactory(); MobilePhone iphone = iphoneFactory.CreateMobilePhone(); //调用工厂对象生产手机的方法 iphone.Show(); //Console.WriteLine(); //工厂方法与简单工厂结合使用 //MobliePhoneFactoryHelper.GetPhone("苹果").Show(); //MobliePhoneFactoryHelper.GetPhone("小米").Show(); Console.ReadKey(); } }
总结:工厂方法模式是对简单工厂模式的改造和升级,在工厂模式的基础上对简单工厂做了更高层次的抽象,抽象出工厂方法接口。
优点:工厂方法模式符合对修改关闭、对扩展开放的原则,具有更好的扩展性。
缺点:但工厂方法模式引入了更高的代码复杂度,学习和调用成本更高,且创建每个类的实例都需要相关的工厂类配合,所以某些情况下需要增加很多相关类。
备注:由于工厂方法在被外界调用时仍存在复杂度较高的问题,所以我们可以用简单工厂与工厂方法结合来解决(所以,没有万能的设计模式,关键是取长补短)。
#region 工厂方法与简单工厂结合 public class MobliePhoneFactoryHelper { public static MobilePhone GetPhone(string name) { IMobilePhoneFactory factory = null; switch (name) { case "苹果": factory = new IPhoneFactory(); return factory.CreateMobilePhone(); case "小米": factory = new XiaoMiFactory(); return factory.CreateMobilePhone(); default: throw new NotImplementedException("不存在的品牌"); } } } #endregion
场景三:
工厂方法虽好,但在面对繁多的产品种类时也会有力不从心的时候。拿小米来说吧,产品系列包括手机、平板、盒子、路由器、电视。。。。,说不定以后还要生产
小米汽车之类的产品。如果我们现在要用工厂方法来实现小米的这种业务需求,那么则需要每个产品对应一个工厂,这样做的后果是资源浪费何其的严重啊。。。
所以,我们提出生产线的概念,一个工厂可以有多条生产线,每条生产线生产不同的产品。然后,我们的脑海中浮现出了这样的场景,某某苹果产品工厂里面有IPhone生产线、
IPad生产线、IMac生产线...。某某小米产品工厂里面有小米手机生产线、小米平板、小米电视生产线...。
以上说了这么多,就是为了诠释抽象工厂来做准备。抽象工厂是对工厂方法模式的改进升级,抽象工厂里面能同时生产多做不同的产品(也就是常说的产品族)。
/// <summary> /// 产品线工厂接口(可生产不同的产品,比如生产手机、平板、电视等等) /// </summary> public interface IProductLineFactory { /// <summary> /// 生产手机 /// </summary> /// <returns></returns> MobilePhone CreateMobilePhone(); /// <summary> /// 生产平板 /// </summary> /// <returns></returns> Pad CreatePad(); } public class AppleProductLineFactory : IProductLineFactory { public MobilePhone CreateMobilePhone() { return new IPhone("iphone5s", 5000); } public Pad CreatePad() { return new IPad("new ipad", 3000); } } public class XiaoMiProductLineFactory : IProductLineFactory { public MobilePhone CreateMobilePhone() { return new XiaoMi("小米2s", 2200); } public Pad CreatePad() { return new XiaoMiPad("小米平板", 1999); } }
调用代码:
class Program { static void Main(string[] args) { //小米生产线 IProductLineFactory xiaomiProductLine = new XiaoMiProductLineFactory(); xiaomiProductLine.CreateMobilePhone().Show(); //生产小米手机 xiaomiProductLine.CreatePad().Show();//生产小米平板 Console.WriteLine(); IProductLineFactory appleProductLine = new AppleProductLineFactory(); appleProductLine.CreateMobilePhone().Show(); //生产苹果手机 appleProductLine.CreatePad().Show();//生产苹果平板 Console.ReadKey(); }
总结:抽象工厂创建的往往是一到多个不同类型的实例,而不是某一个类型的实例。它将对象创建的思想由点到面进行延伸,体现的是一种具体到抽象的思维过程,
所以由此而得名抽象工厂(非官方解释,个人理解而已)
优点:工厂方法模式虽符合了开闭原则,但每个类的对象需要一个对应的工厂,当我们需要创建多种不同类型对象时那么工厂方法类数量就会剧增。
所以,抽象工厂提出了产品族的概念,即相互关联的一类产品创建可以合并到一个实际工厂中的多个方法来创建,避免工厂类数量的爆炸。
缺点:当然,抽象工厂仍存在对客户端调用繁琐的问题,这里也可以和工厂方法一样来借助简单工厂简化调用,具体请参考工厂方法与简单工厂结合代码。
结语:工厂系列中的三大模式是紧密联系的,简单工厂->工厂方法->抽象工厂中的后一种设计模式都是对前一种模式缺陷的改造升级。
当然这里所说的缺陷也要辩证的来对待,在文中有提及,切不可一概而论。如果你觉得我的文章对你有点启迪,那么我的小伙伴不要吝啬给个赞吧。