抽象工厂

来源:互联网 发布:c语言软件源码 编辑:程序博客网 时间:2024/05/14 02:07

由于讨论班上我要讲 Microsoft .Net PetShop 4.0,里面用了工厂模式,所以不得不去了解。我之前听过“设计模式”这个词语,以及“没有写过十万行代码不要去谈设计模式”这句话,但精确的概念我基本上是不知道的。这阵子断断续续地在网上找了些文章看,也翻了些书,自认为模模糊糊地有点理解了,故写篇小记,一来检验自己是否真的有些理解了、能不能用文字描述清楚,二来如果有错误之处可以请大家指出。

对于读者您,您最好已经初步了解了 C#/.Net 和面向对象思想。本文用 C# 描述。

首先,假定有这么两个类——奥迪汽车和波音汽车。(当我自以为风趣地构造出波音造汽车这个词的时候,很无奈地看到波音可能确实造汽车了。)简单起见,只给它们一个方法 Run()。和大多数例程一样,该方法仅仅在控制台输出文字,用于区分是哪个类的方法。

代码如下:

namespace Sample1
{
    // 假设有一个奥迪汽车类和一个波音汽车类
    class AudiCar
    {
        public void Run()
        {
            Console.WriteLine("AudiCar Runs.");
        }
    }

    class BoeingCar
    {
        public void Run()
        {
            Console.WriteLine("BoeingCar Runs.");
        }
    }

    // 测试
    class Program
    {
        static void Main(string[] args)
        {
            AudiCar audiCar = new AudiCar();
            audiCar.Run();

            BoeingCar boeingCar = new BoeingCar();
            boeingCar.Run();
        }
    }
}
 

形象起见,给它们画这么个图:

 

 

很尴尬的一点是,我不懂 UML,因此无法用标准的 UML 来图示,只能根据自己的理解给出一些自认为可以看懂的图示。

自己观察这两个类,会发现它们很相似,可以抽象出它们的父类。C# 语言支持接口这个概念,因此这里抽象出接口。——已经熟悉面向对象的读者可以略过相关内容。

现在代码变成这样子了:

namespace Sample2
{
    // 因为两类汽车都支持 Run() 这个方法(有共性),所以可以抽象出父类,或者抽象出接口
    // 接口定义如下:
    interface ICar
    {
        void Run();
    }

    // 下面是两个具体的汽车定义
    class AudiCar : ICar
    {
        public void Run()
        {
            Console.WriteLine("AudiCar Runs.");
        }
    }

    class BoeingCar : ICar
    {
        public void Run()
        {
            Console.WriteLine("BoeingCar Runs.");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // 使用的时候根据实际需要加以选择
            ICar car = new AudiCar();
            //ICar car = new BoeingCar();
            car.Run();

            // 但是,这里有一个问题
            // 我一旦改变汽车品牌,这里的代码要被改过重新编译
            // 也就是说,离开了开发人员,这个选择是不可改变的
        }
    }
}
 

相应的图示为:

 

 

这是很传统的多态的应用,C++ 教科书上基本上都能找到类似的例子。

在这里我们引出一个问题。在实际应用中,同一接口的两个不同实现经常不是都用到的,可能在一些场合只用某一种。比如刚才的例子中,系统在最初可能会让客户选择使用奥迪还是波音,一旦选定(比如选择了奥迪)之后,以后所有的关于汽车的东西将都由被选择的奥迪汽车来执行——并不需要同时波音汽车。可是,也许有一天,应用环境变了,需要改为使用波音了,这时候问题来了。因为这个选择已经写在了代码里面(ICar car = new AudiCar();),除非重新编译此程序,否则只能继续用奥迪。我们看到写“死”的程序面对变化的需求真是太无力了。所以我们要解决这个灵活性问题,也就是让客户可以决定用奥迪还是波音,并且在开发人员不介入的情况下改变到已部署的系统中去。

以上场景并不复杂,同时跟我们要讲的抽象工厂模式还差了一步。我们继续将场景复杂化,引入第二种产品——飞机,也有奥迪飞机和波音飞机。(奥迪总算不产飞机的吧?)

同样使用接口,代码如下:

namespace Sample3 
{
    // 变动一下场景,新增一个产品——飞机

    interface ICar
    {
        void Run();
    }

    class AudiCar : ICar
    {
        public void Run()
        {
            Console.WriteLine("AudiCar Runs.");
        }
    }

    class BoeingCar : ICar
    {
        public void Run()
        {
            Console.WriteLine("BoeingCar Runs.");
        }
    }

    // 飞机接口定义
    interface IPlane
    {
        void Fly();
    }

    class AudiPlane : IPlane
    {
        public void Fly()
        {
            Console.WriteLine("AudiPlane Flies.");
        }
    }

    class BoeingPlane : IPlane
    {
        public void Fly()
        {
            Console.WriteLine("BoeingPlane Flies.");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // 使用的时候根据实际需要加以选择
            ICar car = new AudiCar();
            //ICar car = new BoeingCar();
            car.Run();

            //IPlane plane = new AudiPlane();
            IPlane plane = new BoeingPlane();
            car.Run();

            // Sample2 中的问题还未解决
            // 这里还可以看到,随着“物品”(汽车、飞机)的增加
            // 要改动的地方会越来越多
            // 也就是维护成本会变得越来越高
        }
    }
}
 

相应的图示变为:

 

 

 

场景复杂了,但是灵活性问题还没解决。我们看到,场景是可以任意复杂下去的,只要不断地增加“物品”即可。再来点奥迪/波音卡车、奥迪/波音高射炮之类的,之后的维护代价会大大增加。

下面得引出“工厂”的概念了,先不忙着讲,请看代码,注意最后两个类 AudiFactory 和 BoeingFactory:

namespace Sample4
{
    // 场景不变

    interface ICar
    {
        void Run();
    }

    class AudiCar : ICar
    {
        public void Run()
        {
            Console.WriteLine("AudiCar Runs.");
        }
    }

    class BoeingCar : ICar
    {
        public void Run()
        {
            Console.WriteLine("BoeingCar Runs.");
        }
    }

    interface IPlane
    {
        void Fly();
    }

    class AudiPlane : IPlane
    {
        public void Fly()
        {
            Console.WriteLine("AudiPlane Flies.");
        }
    }

    class BoeingPlane : IPlane
    {
        public void Fly()
        {
            Console.WriteLine("BoeingPlane Flies.");
        }
    }

    // 新增两个类,用于“生产”汽车和飞机
    // 所以这两个类叫做“工厂”

    // 奥迪工厂
    class AudiFactory
    {
        public ICar CreateCar()
        {
            return new AudiCar();
        }

        public IPlane CreatePlane()
        {
            return new AudiPlane();
        }
    }

    // 波音工厂
    class BoeingFactory
    {
        public ICar CreateCar()
        {
            return new BoeingCar();
        }

        public IPlane CreatePlane()
        {
            return new BoeingPlane();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // 这个时候把对产品的选择转移到了对工厂的选择
            // 暂不测试,继续看下例
        }
    }
}
 

最后面这两个类就是所谓的“工厂”,但不是抽象工厂,是具体工厂。具体工厂并不是为了解决上文提出的灵活性问题的,而是解决对象构造问题的。在有工厂的系统中,产品类(AudiCar、BoeingCar、AudiPlane、BoeingPlane)往往是不被直接构造的。原因呢,看到有一篇文章里说,可能需要很多初始化工作要完成,而这些工作是不适合放在构造函数的。但也不尽是这样。现在不妨理解为为了使用工厂而是用工厂吧。

现在,要得到一个产品对象,得先有一个工厂对象,再调用相应的方法,而不是直接 new 了。当然,在这样的情形下,上面的工厂类可以用静态类,之所以没有,是为了下面要讲的抽象工厂。

很明显地看到两个工厂具有共性,于是可以抽象出他们的父类。这里不使用接口,因为我希望这个父类有自己的内容——提供一个静态方法用于产生具体工厂的对象。

代码如下:

namespace Sample5 
{
    // 场景不变

    interface ICar
    {
        void Run();
    }

    class AudiCar : ICar
    {
        public void Run()
        {
            Console.WriteLine("AudiCar Runs.");
        }
    }

    class BoeingCar : ICar
    {
        public void Run()
        {
            Console.WriteLine("BoeingCar Runs.");
        }
    }

    interface IPlane
    {
        void Fly();
    }

    class AudiPlane : IPlane
    {
        public void Fly()
        {
            Console.WriteLine("AudiPlane Flies.");
        }
    }

    class BoeingPlane : IPlane
    {
        public void Fly()
        {
            Console.WriteLine("BoeingPlane Flies.");
        }
    }

    // 又看到两个工厂也有共性,于是又可以抽象出父类
    // 因为这个类里要写一个非抽象的方法 GetFactory(),所以不用接口而用抽象类
    abstract class AbstractFactory
    {
        public static AbstractFactory GetFactory(string factoryType)
        {
            switch (factoryType)
            {
                case "Audi":
                    return new AudiFactory();
                case "Boeing":
                    return new BoeingFactory();
                default:
                    break;
            }
            throw new Exception("No such factory.");
        }

        public abstract ICar CreateCar();
        public abstract IPlane CreatePlane();
    }

    // 奥迪工厂
    class AudiFactory : AbstractFactory
    {
        public override ICar CreateCar()
        {
            return new AudiCar();
        }

        public override IPlane CreatePlane()
        {
            return new AudiPlane();
        }
    }

    // 波音工厂
    class BoeingFactory : AbstractFactory
    {
        public override ICar CreateCar()
        {
            return new BoeingCar();
        }

        public override IPlane CreatePlane()
        {
            return new BoeingPlane();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            string factory = "Audi";
            // 实际上这时已经解决了 Sample2 末尾的问题了
            // 这个字符串完全可以保存在程序外部(如配置文件中)
            // 运行的时候读入即可
            // 无需重新编译代码即可实现汽车品牌的选择
            // string factory = ConfigurationManager.AppSettings["Factory"];

            AbstractFactory f = AbstractFactory.GetFactory(factory);
            ICar car = f.CreateCar();
            car.Run();
            IPlane plane = f.CreatePlane();
            plane.Fly();
        }
    }

    // 至此我们已经使用了“抽象工厂”设计模式
    // 例中 AbstractFactory 叫做抽象工厂,负责实例化一个具体工厂
    // AudiFactory 和 BoeingFactory 是具体工厂,负责生产产品
}
 

系统图示变为:

 

 

 

注意新增加的类 AbstractFactory,这就是本文的主角“抽象工厂”——它生产具体工厂。生产具体工厂的方法为 GetFactory()。这时,具体工厂也不是直接构造了,而改由抽象工厂构造。要得到一个具体工厂,就:
    AbstractFactory f = AbstractFactory.GetFactory(factory);
参数 factory 是一个字符串,有效的选择是“Audi”或者“Boeing”。实际上现在我们已经解决了上面提出的灵活性问题了。为什么呢?我们看到,代码中对类的选择已经由一个字符串控制了,这个参数 factory 完全可以不用像string factory = "Audi"; 这样写在程序里面了。我们可以将它写在程序外部,如配置文件 App.config 中:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="Factory" value="Audi"/>
  </appSettings>
</configuration>
 

现在可以把
    string factory = "Audi";
换成
    string factory = ConfigurationManager.AppSettings["Factory"];
了,这样,程序中便不出现 Audi 或 Boeing 的选择了,要改变选择,改配置文件即可。

以上说的是传统意义上的抽象工厂。

接下来,我们来看看 .Net 技术下抽象工厂的一些变化。从上面的例子来看,我们本来是直接把类的选择写定在代码里的,后来变成了由一个存储在程序外部的字符串控制,从而解决了灵活性问题的。然而,对这个存储在外部字符串,我们在程序里要来个 switch case,这样显得很难看,如果能够直接给个类名字串就能创建对象(如,ICar = new GetClass(“AudiCar”)),那就方便了。.Net 的反射机制正好为此提供了可能。

所谓反射,抄一段网上的话——

 “程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程序集、模块和类型的对象。您可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性。”

初步理解,好像可以从已编译的程序集中获取类型信息。先不管这么多了,反正我们现在在自己的系统中,可以看到自己的源代码,只要事先给出类名字符串得到类的对象就行了。

我们来改造刚才的抽象工厂类 AbstractFactory:

namespace Sample6
{
    // 来看看 .Net 中的新技术给设计模式带来的改变

    interface ICar
    {
        void Run();
    }

    class AudiCar : ICar
    {
        public void Run()
        {
            Console.WriteLine("AudiCar Runs.");
        }
    }

    class BoeingCar : ICar
    {
        public void Run()
        {
            Console.WriteLine("BoeingCar Runs.");
        }
    }

    interface IPlane
    {
        void Fly();
    }

    class AudiPlane : IPlane
    {
        public void Fly()
        {
            Console.WriteLine("AudiPlane Flies.");
        }
    }

    class BoeingPlane : IPlane
    {
        public void Fly()
        {
            Console.WriteLine("BoeingPlane Flies.");
        }
    }

    // 使用反射技术,可以去掉难看的 switch case
    // 何谓反射,可以先不管,这里它给我们带来的用处就是——提供类名字符串,获得该类型的对象
    abstract class AbstractFactory
    {
        public static AbstractFactory GetFactory(string factoryType)
        {
            return (AbstractFactory)Activator.CreateInstance(Type.GetType("Sample6." + factoryType + "Factory"));
        }

        public abstract ICar CreateCar();
        public abstract IPlane CreatePlane();
    }

    class AudiFactory : AbstractFactory
    {
        public override ICar CreateCar()
        {
            return new AudiCar();
        }

        public override IPlane CreatePlane()
        {
            return new AudiPlane();
        }
    }

    class BoeingFactory : AbstractFactory
    {
        public override ICar CreateCar()
        {
            return new BoeingCar();
        }

        public override IPlane CreatePlane()
        {
            return new BoeingPlane();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            string factory = ConfigurationManager.AppSettings["Factory"];

            AbstractFactory f = AbstractFactory.GetFactory(factory);
            ICar car = f.CreateCar();
            car.Run();
            IPlane plane = f.CreatePlane();
            plane.Fly();
        }
    }

    // 还可以进一步改进,见下例
}
 

其他的都没变,变的仅仅是 GetFactory() 方法,原来一大串变成了一句话。看:
    Activator.CreateInstance(Type.GetType("Sample6." + factoryType + "Factory"));
Type.GetType() 可以从一个类名得到含有该类的信息的 Type 对象,注意这里的参数是完整的类名,要包含命名空间。然后 Activator.CreateInstance() 可以由含该类的信息的 Type 对象创建这个类的实例。在这个例子中,我们这样就去掉了 swich case。

其实可以将反射应用地更加彻底一些。可能在看上面的例程的时候您已经想到了,何不只用一个工厂类,在每个 CreateXXX() 的函数里来判断呢?可是看到一段 switch,又否定了自己的想法了。没错,刚才不这样做正因为有着这段不好看的代码在,这应该也是引入抽象工厂的原因之一吧,至少让这段 switch case 的出现次数减少到了仅有的一次。而现在,没有了 switch case,我们判别不同的类只需要一句话,何不去掉抽象工厂类呢?

就这么干:

namespace Sample7
{
    // 前面部分一样

    interface ICar
    {
        void Run();
    }

    class AudiCar : ICar
    {
        public void Run()
        {
            Console.WriteLine("AudiCar Runs.");
        }
    }

    class BoeingCar : ICar
    {
        public void Run()
        {
            Console.WriteLine("BoeingCar Runs.");
        }
    }

    interface IPlane
    {
        void Fly();
    }

    class AudiPlane : IPlane
    {
        public void Fly()
        {
            Console.WriteLine("AudiPlane Flies.");
        }
    }

    class BoeingPlane : IPlane
    {
        public void Fly()
        {
            Console.WriteLine("BoeingPlane Flies.");
        }
    }

    // 这次我们去掉抽象工厂
    // 去不到终点回到原点?不是。
    static class Factory
    {
        static string factory = ConfigurationManager.AppSettings["Factory"];

        public static ICar CreateCar()
        {
            return (ICar)Activator.CreateInstance(Type.GetType("Sample7." + factory + "Car"));
        }

        public static IPlane CreatePlane()
        {
            return (IPlane)Activator.CreateInstance(Type.GetType("Sample7." + factory + "Plane"));
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ICar car = Factory.CreateCar();
            car.Run();
            IPlane plane = Factory.CreatePlane();
            plane.Fly();
        }
    }

    // 在这里,抽象工厂形式上退化为一个具体工厂
    // 然而在设计上它仍然算是一个抽象工厂
    // 这是被称为“.Net 反射工厂”,是抽象工厂在 .Net 中的变体
    // PetShop 中大量使用了这种反射工厂(如 DALFactory 等)
}
 

我们的代码改造到此为止。我们的系统最终变成了如下的样子:

 

 

看上去是不是清爽多了?在这里,我们的设计思想仍然是抽象工厂,但是从形式上来看,它已经是一个具体工厂(简单工厂?)了。这就是抽象工厂在应用 .Net 反射技术之后的变体,被称为“.Net 反射工厂”。PetShop 中大量使用了这种反射工厂,如 DALFactory。

网上关于工厂模式的文章中,往往都一起谈到简单工厂、工厂方法、抽象工厂。细心的读者可能注意到,我在上文中几乎只字不提前两个。为什么呢?因为我还没搞清楚它们的区别,现在我仅仅理解了抽象工厂(也许还没理解)。我也看过那些文章,但是呢,不客气地说,它们没有把三者区分清楚。如果在一篇文章中同时谈到这三者,我认为,至少场景应该是同一体系。比如我如果在这里要谈简单工厂和工厂方法,我肯定也会用奥迪汽车、波音汽车作例子,或许会做些改变,比如变成奥迪玩具汽车、奥迪真实汽车、波音玩具汽车、波音真实汽车等等,只要有需要都可以变,但不管怎么样,要同一体系的东西,否则很难具有辨析度。目前我看到的一些文章中,通常它们换个模式就换个场景,而且有些讲得也不是非常清楚,或者场景过于庞大。如果那位看官能给出这样的例子那最好不过了,呵呵。

最后,如果我有理解错误的,请各位不吝指出,这也是我发表这篇文章的目的之一。曾看到有篇文章楼主在讲抽象工厂,后面有评论说“只不过是个简单工厂而已”,希望我没犯这样的错误。呵呵。

 

溪流
2008年12月6日

 

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/cnStreamlet/archive/2008/12/08/3478674.aspx