聊聊工厂模式

昨天写代码的时候发现大多代码都一样,只有类型不一样,当时脑海里就冒出“工厂模式”的概念,但又说不清到底什么是工厂模式,我所遇到的情况又到底适不适合使用工厂模式,于是花时间好好把工厂模式看了一下,想通过这篇文章来输出我所看到和get到的内容。

我比较喜欢旅游,就拿旅游产品来举例子吧。

简单工厂

首先抽象一个旅游产品接口,该接口有一个获取线路列表的行为:

    /// <summary>
    /// 旅游产品接口
    /// </summary>
    public interface ITravelProduct
    {
        /// <summary>
        /// 获取旅游产品线路列表
        /// </summary>
        /// <returns>The line list.</returns>
        string GetLineList();
    }

旅游产品有很多种,比如国内游(具体的产品类):

    /// <summary>
    /// 国内游
    /// </summary>
    public class DomesticTravel:ITravelProduct
    {
        public string GetLineList() => "您获取到国内游的线路列表啦!";
    }

再比如出境游(具体的产品类):

    /// <summary>
    /// 出境游
    /// </summary>
    public class OutboundTravel:ITravelProduct
    {
        public string GetLineList() => "您获取到出境游的线路列表啦!";
    }

再比如邮轮游(具体的产品类):

    /// <summary>
    /// 邮轮游
    /// </summary>
    public class CruiseTravel:ITravelProduct
    {
        public string GetLineList() => "您获取到邮轮游的线路列表啦!";
    }

然后我们来到一家OTA,看到网站上提供了如下几种旅游产品的线路列表(简单工厂类):

    /// <summary>
    /// 简单工厂模式的工厂类
    /// </summary>
    public class SimpleTravelProductFactory
    {
        public const int Domestic = 1;//国内游
        public const int Outbond = 2;//出境游
        public const int Cruise = 3;//邮轮

        public static ITravelProduct createTravelProduct(int type){
            switch(type){
                case Domestic:
                    return new DomesticTravel();
                case Outbond:
                    return new OutboundTravel();
                case Cruise:
                    return new CruiseTravel();
                default:
                    return new DomesticTravel();

            }
        }
    }

下面就看你想选择哪种旅游产品啦,比如你想选个国内游的线路列表看看,那么,我们就让简单工厂给我们返回一下国内游的线路列表:

        public static void Main(string[] args)
        {
            Console.WriteLine("=========简单工厂模式==========");
            string domesticLineList = SimpleTravelProductFactory.createTravelProduct(SimpleTravelProductFactory.Domestic).GetLineList();
            Console.WriteLine(domesticLineList);
        }

输出结果:

=========简单工厂模式==========
您获取到国内游的线路列表啦!

如果你想获取出境游的线路列表活着邮轮游的线路列表,只需要在创建旅游产品时传入对应的产品类型即可。
显而易见,简单工厂的缺点就是,如果有很多很多种产品类型,那么你工厂类中的switch,case语句会很长,而且在做产品的扩展时,除了要增加具体的产品类外,还需要修改工厂类(往工厂类中增加case语句呀),这种情况下,你的产品和你的工厂还是有较强的耦合的,要解决这个问题,有两个方法:
一是利用反射实现简单工厂模式;二是使用工厂模式。
在这之前,我们先来看看简单工厂模式中工厂类的另一种写法:

    /// <summary>
    /// 简单工厂模式工厂类的另一种写法
    /// </summary>
    public class MulWayTravelProductFactory
    {
        /// <summary>
        /// 直接指定创建的产品
        /// </summary>
        /// <returns>The domestic travel product.</returns>
        public static ITravelProduct createDomesticTravelProduct()
        {
            return new DomesticTravel();
        }

        public static ITravelProduct createOutboundTravelProduct()
        {
            return new OutboundTravel();
        }

        public static ITravelProduct createCruiseTravelProduct()
        {
            return new CruiseTravel();
        }
    }

此时,查看国内游线路列表的写法如下:

Console.WriteLine("=========简单工厂模式工厂类的另一种写法==========");
string domesticLineList = MulWayTravelProductFactory.createDomesticTravelProduct().GetLineList();
Console.WriteLine(domesticLineList);

输出结果:

=========简单工厂模式工厂类的另一种写法==========
您获取到国内游的线路列表啦!

这种方法虽然摆脱了switch,case语句,但依然摆脱不了具体的产品类和工厂类的耦合。所以,下面,我们就来看看利用反射实现的简单工厂模式吧!

利用反射实现的简单工厂模式

我们只需把工厂类生产产品的方式改成反射的方式即可。这里主要是用到了反射中,根据类名获取类实例的知识点,直接看代码吧:

    /// <summary>
    /// 利用反射实现的简单工厂模式
    /// </summary>
    public class ReflectTravelProductFactory
    {
        /// <summary>
        /// Creates the travel product.
        /// </summary>
        /// <returns>The travel product.</returns>
        /// <param name="fullname">类名(包括命名空间的类名)</param>
        /// <typeparam name="T">The 1st type parameter.</typeparam>
        public static T createTravelProduct<T>(string fullname)
        {
            Assembly assenbly = Assembly.GetExecutingAssembly();
            return (T)assenbly.CreateInstance(fullname);
        }
    }

需要注意两点:

  • 参数fullname就是类名,但是完全限定名,也就是说要包括命名空间的
  • CreateInstance创建的类实例其实是object类型的,需要强制转换一下

此时,查看国内游线路列表的写法为:

Console.WriteLine("=========利用反射实现的简单工厂模式==========");
string domesticLineList = ReflectTravelProductFactory.createTravelProduct<DomesticTravel>("factorypatterndemo.TravelProduct.DomesticTravel").GetLineList();
Console.WriteLine(domesticLineList);

输出结果:

=========利用反射实现的简单工厂模式==========
您获取到国内游的线路列表啦!

此时,具体的产品类跟工厂类已经完全解耦了。也就是说,如果需要做产品的扩展,只需要增加具体的产品类,无需修改工厂类了。
但这个方式唯一的缺点(准确点讲是问题吧)就是反射的性能有待商榷。

工厂模式

上面所有方式的做法就是在一个工厂里生产了所有的产品,工厂模式的做法是抽象出一个工厂接口,然后不同的产品在不同的工厂中进行生产。
(1)抽象出一个工厂接口:

    /// <summary>
    /// 抽象工厂接口
    /// </summary>
    public interface ITravelProductFactory
    {
        ITravelProduct getTravelProductType();//该接口有一个获取旅游产品类型的行为
    }

(2)不同的产品建立不同的工厂
国内游产品的工厂:

    /// <summary>
    /// 生产国内游产品的工厂
    /// </summary>
    public class DomesticTravelFactory:ITravelProductFactory
    {
        public ITravelProduct getTravelProductType()
        {
            return new DomesticTravel();
        }
    }

出境游产品的工厂:

    /// <summary>
    /// 生产出境游产品的工厂
    /// </summary>
    public class OutboundTravelFactory:ITravelProductFactory
    {
        public ITravelProduct getTravelProductType()
        {
            return new OutboundTravel();
        }
    }

邮轮游产品的工厂:

    /// <summary>
    /// 生产邮轮游产品的工厂
    /// </summary>
    public class CruiseTravelFactory:ITravelProductFactory
    {
        public ITravelProduct getTravelProductType()
        {
            return new CruiseTravel();
        }
    }

此时,查看国内游线路列表的写法如下:

Console.WriteLine("=========工厂模式==========");
ITravelProductFactory factory = new DomesticTravelFactory();
ITravelProduct domesticTravel= factory.getTravelProductType();
string domesticLineList = domesticTravel.GetLineList();
Console.WriteLine(domesticLineList);

输出结果:

=========工厂模式==========
您获取到国内游的线路列表啦!

如果要�扩展�产品,只需要增加对应的产品类和产品工厂即可。

大家知道,OTA不止一家,每家都有国内游、出境游、邮轮游,此时抽象工厂模式即将登场。

抽象工厂

(1)分别建立国内游、出境游和邮轮游的抽象接口,并继承一开始定义的ITravelProduct接口,这样就可以共用ITravelProduct接口中定义的行为了。代码如下:

    public interface IDomesticTravel:ITravelProduct
    {
    }
    public interface IOutboundTravel:ITravelProduct
    {
    }
    public interface ICruiseTravel:ITravelProduct
    {
    }

(2)定义一个OTA抽象工厂,该工厂可以生产国内游、出境游和邮轮游三种产品:

    public interface ITravelFactory
    {
        IDomesticTravel createDomesticTravel();
        IOutboundTravel createOutboundTravel();
        ICruiseTravel createCruiseTravel();
    }

(3)定义一家名叫XC的OTA工厂,该工厂继承ITravelFactory

    public class XC_Factory:ITravelFactory
    {
        public IDomesticTravel createDomesticTravel()
        {
            return new XC_DomesticTravel();
        }

        public IOutboundTravel createOutboundTravel()
        {
            return new XC_OutboundTravel();
        }

        public ICruiseTravel createCruiseTravel()
        {
            return new XC_CruiseTravel();
        }
    }

再定义一家名叫TC的OTA工厂:

    public class TC_Factory : ITravelFactory
    {
        public ICruiseTravel createCruiseTravel()
        {
            return new TC_CruiseTravel();
        }

        public IDomesticTravel createDomesticTravel()
        {
            return new TC_DomesticTravel();
        }

        public IOutboundTravel createOutboundTravel()
        {
            return new TC_OutboundTravel();
        }
    }

现在,我们想要获取两家OTA的所有国内游线路列表:

Console.WriteLine("=========抽象工厂模式==========");
ITravelFactory tc_factory = new TC_Factory();
ITravelFactory xc_factory = new XC_Factory();
IDomesticTravel tc_domesticTravel = tc_factory.createDomesticTravel();
IDomesticTravel xc_domesticTravel = xc_factory.createDomesticTravel();
string tc_domesticLineList = tc_domesticTravel.GetLineList();
string xc_domesticLineList = xc_domesticTravel.GetLineList();
Console.WriteLine(tc_domesticLineList);
Console.WriteLine(xc_domesticLineList);

输出结果:

=========抽象工厂模式==========
您获取到TC的国内游线路列表啦!
您获取到XC的国内游线路列表啦!

好啦,到这里,工厂模式基本讲结束了。总结一下:

  • 工厂模式有三种:简单工厂、工厂和抽象工厂
  • 简单工厂模式中,产品类和工厂类之间存在耦合
  • 工厂模式只能解决单一商家多个产品的问题
  • 抽象工厂模式可以解决多个商家多个产品的问题

Q&A环节
Q:上面的代码中都是使用接口来表示抽象产品或者抽象工厂,可以改成用抽象类吗?
(这个问题翻译一下,其实就是抽象类和接口的区别)
A:就本实例中的接口而言,完全可以使用抽象类替换。但不是说抽象类和接口就是等价的,不然为什么会同时存在呢?存在即合理嘛,一般对象的抽象偏向使用抽象类,行为的抽象偏向使用接口。抽象类中除了有无需实现的抽象方法,也可以有具体实现的方法;而接口中只能有无实现的方法。一个类只能继承一个父类,但可以继承多个接口,所以我们在使用接口的时候有更好的扩展性,这大概就是编程原则中有一条是针对接口编程,而不是针对类编程吧。

源码地址:

差不多就这些了。

说些题外话,最近看到身边的同事小伙伴们在自己坚持不懈地努力下,慢慢成长和蜕变成更加优秀的样子,有时候我常常想,如果我一开始认识此刻的TA,大概都不敢和TA交朋友吧。没有谁天生就是优秀的,每个优秀的人背后都付出了旁人难以想象和无法看到的努力和坚持。也许我还是对自己要求太低了,由于种种原因放松了对自我成长的要求,看着自己与伙伴越来越大的差距,既羞愧又紧张,也有不少压力,也许是我还没放弃自己吧,如果要自己立刻和如此优秀的他们比,也许我连重新上路的勇气都没有,所以就和昨天的自己比吧。其实这篇文章昨天就能完成的,但我还是拖延到今天才完成,不过好在今天完成了,说明比昨天的我优秀了一点点,哈哈,只能这么安慰自己了,加油吧!

    原文作者:心彻
    原文地址: https://www.jianshu.com/p/d17351521c80
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞