代码改变世界

抽象工厂模式(Abstract Factory)

2011-09-29 21:38  javaspring  阅读(301)  评论(0编辑  收藏  举报
一、概要

        抽象工厂模式(Abstract Factory),提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。抽象工厂模式提供一种“封装机制”用来解决“多系列具体对象”的创建工作,降低客户端和“创建工作”的紧耦合度。


二、生活中的例子

         例如一个汽车工厂(抽象工厂),拥有四个下属汽车工厂(具体工厂),分别是生产轿车,越野车,客车,每个工厂都要有生产各自类型汽车配件的车间(方法)。

         考虑到开闭原则,增加新车型工厂容易,增加新型配件不容易。

图示:


三、实现思路

        当有多个抽象产品,且每个抽象产品都有多于一个的具体子类的时候,而抽象工厂角色又提供多个具体的工厂角色,分别对应多个具体的产品角色,每个具体工厂角色负责实例化一系列具体的产品,即抽象工厂模式针对的是多个产品等级结构。

 

四、类图


五、注意点
1、在产品族中扩展新的产品是很困难的,它需要修改抽象工厂的接口,例如上面生活例子中,如果要增加汽车玻璃这一产品,那么就需要增加汽车工厂接口中的方法,不符合开闭原则。
2、增加产品系列容易,例如上面生活例子中,要增加一款新款汽车,例如面包车,则只需要增加一个面包车工厂类,然后再增加相应的具体产品类即可,符合开闭原则。
 
六、实例
说明:下面我们通过一个例子循序渐进的介绍抽象工厂模式。当然这个例子是理想化中的实例,可能有些牵强,只为说明问题
语言:C#
 
需求1.0:
学校A机房的《收费系统》里针对不同的用户有不同的计费方式:会员1元/小时,非会员1.5元/小时
 
代码1.0实现:

using System;

namespace 抽象工厂模式实例一
{
    class Program
    {
        static void Main(string[] args)
        {

            ChargeType Muser = new SchoolA_MemberCharge();
            ChargeType Nuser = new SchoolA_NormalCharge();

            Muser.Charge(2);
            Nuser.Charge(2);
        }
    }


    interface ChargeType
    {
        void Charge(int intHour);
    }

    //会员计费类
    public class SchoolA_MemberCharge:ChargeType 
    {
        public void Charge(int intHour)
        {
            Console.WriteLine("您是会员,您消费了{0}小时,收费{1}元", intHour, intHour * 1);
        }
    }

    //普通用户计费类
    public class SchoolA_NormalCharge : ChargeType 
    {
        public void Charge(int intHour)
        {
            Console.WriteLine("您是一般用户,您消费了{0}小时,收费{1}元", intHour, intHour * 1.5);
        }
    }

}

运行结果:您是会员,您消费了2小时,收费2元

                      您是一般用户,您消费了2小时,收费3元

 

需求2.0:这个时候,又有另一所学校B对这个收费系统感兴趣,也想引进一款这样的系统。但是,学校B的收费标准和学校A不同,

他们的标准是:会员1.5元,一般用户2元。

 

问题:根据上面系统的实现,我们很容易会想到,只要把学校A的会员计费类SchoolA_MemberCharge和一般用户计费类SchoolA_NormalCharge改成学校B的SchoolB_NormalCharge和SchoolB_NormalCharge类即可,再修改相应的客户端代码,就可以拿到学校B去用。这个时候,如果学校C也想购买这套系统,学校C的收费标准和学校A相同,这个时候我们又需要把刚才的两个类改回去。

 

即每当这个系统移植到别的学校的时候,我们都需要改变这两个类。这显然违背了我们面向对象程序设计的基本原则:开闭原则,最少修改原则(当然还有更好的方式)。

 

所以,我们需要整合成通用系统。一个可以立即想到的做法就是在系统中保留所有业务规则模型,即会员计费和一般用户的运算规则。假设:学校的计费规则无非就这两种,第一种:会员1.5元,一般用户2元;第二种:会员1元,一般用户1.5元。再次移植系统的时候,我们只需要改变客户端调用就可以了。

 

下面,就是我们抽象工厂模式一展拳脚的时刻了。

类图:


代码2.0

using System;

namespace 抽象工厂模式实例二
{
    class Program
    {
        static void Main(string[] args)
        {
            //如果是学校A,则使用这段代码
            IChargeTypeFactory factoryA = new SchoolA_ChargeFactory();
            IMemberCharge imemberA=factoryA.CreateMemberCharge();
            imemberA.Charge(2);

            INormalCharge inormalA=factoryA.CreateNormalCharge();
            inormalA.Charge(2);

            //如果是学校B,则使用这段代码
            //IChargeTypeFactory factoryB= new SchoolB_ChargeFactory();
            //IMemberCharge imemberB = factoryB.CreateMemberCharge();
            //imemberB.Charge(2);

            //INormalCharge inormalB = factoryB.CreateNormalCharge();
            //inormalB.Charge(2);
        }
    }


    //定义一个创建访问会员计费对象和一般用户计费对象的抽象的工厂接口
    interface IChargeTypeFactory
    {
        //创建“会员计费“产品的方法
       IMemberCharge CreateMemberCharge();
        //创建”一般用户计费“的方法
       INormalCharge CreateNormalCharge();
    }


    //实现IChargeTypeFactory接口,实例化SchoolA_MemberCharge和SchoolA_NormalCharge
    class SchoolA_ChargeFactory : IChargeTypeFactory
    {
        public IMemberCharge CreateMemberCharge()
        {
            return new SchoolA_MemberCharge();
        }

        public INormalCharge CreateNormalCharge()
        {
            return new SchoolA_NormalCharge(); 
        }
    }


    //实现IChargeTypeFactory接口,实例化SchoolB_MemberCharge和SchoolB_NormalCharge
    class SchoolB_ChargeFactory : IChargeTypeFactory
    {
        public IMemberCharge CreateMemberCharge()
        {
            return new SchoolB_MemberCharge();
        }

        public INormalCharge CreateNormalCharge()
        {
            return new SchoolB_NormalCharge();
        }
    }

    //该接口用于客户端访问,解除与创建具体会员收费标准的耦合
    interface IMemberCharge
    {
        void Charge(int intHour);
    }
    
    //学校A的会员收费标准类
    class SchoolA_MemberCharge : IMemberCharge
    {
        public void Charge(int intHour)
        {
            Console.WriteLine("您是学校A的会员,您消费了{0}小时,收费{1}元", intHour, intHour * 1);
        }
    }

    //学校B的会员收费标准类
    class SchoolB_MemberCharge : IMemberCharge
    {
        public void Charge(int intHour)
        {
            Console.WriteLine("您是学校B的会员,您消费了{0}小时,收费{1}元", intHour, intHour * 1.5);
        }
    }

    //该接口用于客户端访问,解除与创建具体一般用户收费标准的耦合
    interface INormalCharge
    {
        void Charge(int intHour);
    }

    //学校A的一般用户收费标准类
    class SchoolA_NormalCharge : INormalCharge
    {
        public void Charge(int intHour)
        {
            Console.WriteLine("您是学校A的一般用户,您消费了{0}小时,收费{1}元", intHour, intHour * 1.5);
        }
    }
    
    //学校B的一般用户收费标准类
    class SchoolB_NormalCharge : INormalCharge
    {
        public void Charge(int intHour)
        {
            Console.WriteLine("您是学校B的一般用户,您消费了{0}小时,收费{1}元", intHour, intHour * 2);
        }
    }

}

我们发现,上述代码,如果我们需要在两所学校之间移植,仍然需要修改客户端代码。
这时候问题就来了:(需求3.0)如果这个时候,我们让公司的一些员工去负责移植个系统,由于员工权限不够,他们不能获得该系统的源代码。这就导致根本不能移植成功。
 
不断遇到问题,不断解决问题,自然便会进步。
 
这时候我们可以增加一个静态方法,该方法根据一个配置文件(App.config或者Web.config)的一个项(比如factoryName)动态地判断应该实例化哪个工厂类,这样,我们就把移植工作转移到了对配置文件的修改。
 
类图:



代码3.0:

using System;
using System.Reflection;
using System.Configuration;

namespace 抽象工厂模式实例三
{
    class Program
    {
        static void Main(string[] args)
        {

                IChargeTypeFactory Ifactory = FactoryChoose.createFactory();
                
                IMemberCharge Imember = Ifactory.CreateMemberCharge();
                Imember.Charge(2);

                INormalCharge Inormal = Ifactory.CreateNormalCharge();
                Inormal.Charge(2);
                
        }
    }

    //根据反射技术和配置文件,选择相应的工厂
    abstract class FactoryChoose
    {
        private static readonly string AssemblyName = "抽象工厂模式实例";
        private static readonly string className= "抽象工厂模式实例三" + "." + ConfigurationManager.AppSettings["className"];

        
        public static IChargeTypeFactory createFactory()  
        {
            return (IChargeTypeFactory)Assembly.Load(AssemblyName).CreateInstance(className);   
        }
    }

         // 其他部分跟代码2.0相同

配置文件:

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

</configuration>


这个时候,即使不知道源代码,我们也可以通过配置文档说明书来轻松地移植该系统了,如果要从学校A移植到学校B,仅仅需要修改配置文件,将className的值改为SchoolB_ChargeFactory。根本不需要修改代码重新编译。
 
进一步讨论,我们既然可以利用反射技术直接实例化SchoolA_ChargeFactory对象,那么显然也可以直接实例化SchoolA_MemberCharge和SchoolA_NormalCharge。由此可以看到,(需求4.0)我们可以把IChargeTypeFactory和SchoolA_ChargeFactory,SchoolB_ChargeFactory合并成一个类FactoryChoose,利用反射技术和配置文件,直接实例化出需要的具体的计费产品对象,这样代码显得简洁明了,不会那么臃肿。


类图:



代码4.0(最后版本)

using System;
using System.Reflection;
using System.Configuration;

namespace 抽象工厂模式实例三
{
    class Program
    {
        static void Main(string[] args)
        {
                IMemberCharge Imember = FactoryChoose.CreateMemberCharge();
                Imember.Charge(2);

                INormalCharge Inormal = FactoryChoose.CreateNormalCharge();
                Inormal.Charge(2);       
        }
    }

    //根据配置文件,选择相应的工厂
    abstract class FactoryChoose
    {
        private static readonly string AssemblyName = "抽象工厂模式实例";
        private static readonly string strSchool = "抽象工厂模式实例三" + "." + ConfigurationManager.AppSettings["School"];

        public static IMemberCharge CreateMemberCharge()  
        {
            string className = strSchool + "_MemberCharge";
            return (IMemberCharge)Assembly.Load(AssemblyName).CreateInstance(className);   
        }

        public static INormalCharge CreateNormalCharge()
        {
            string className = strSchool + "_NormalCharge";
            return (INormalCharge)Assembly.Load(AssemblyName).CreateInstance(className);
        }

    }

    //该接口用于客户端访问,解除与创建具体会员收费标准的耦合
    interface IMemberCharge
    {
        void Charge(int intHour);
    }
    
    //学校A的会员收费标准类
    class SchoolA_MemberCharge : IMemberCharge
    {
        public void Charge(int intHour)
        {
            Console.WriteLine("您是学校A的会员,您消费了{0}小时,收费{1}元", intHour, intHour * 1);
        }
    }

    //学校B的会员收费标准类
    class SchoolB_MemberCharge : IMemberCharge
    {
        public void Charge(int intHour)
        {
            Console.WriteLine("您是学校B的会员,您消费了{0}小时,收费{1}元", intHour, intHour * 1.5);
        }
    }

    //该接口用于客户端访问,解除与创建具体一般用户收费标准的耦合
    interface INormalCharge
    {
        void Charge(int intHour);
    }

    //学校A的一般用户收费标准类
    class SchoolA_NormalCharge : INormalCharge
    {
        public void Charge(int intHour)
        {
            Console.WriteLine("您是学校A的一般用户,您消费了{0}小时,收费{1}元", intHour, intHour * 1.5);
        }
    }
    
    //学校B的一般用户收费标准类
    class SchoolB_NormalCharge : INormalCharge
    {
        public void Charge(int intHour)
        {
            Console.WriteLine("您是学校B的一般用户,您消费了{0}小时,收费{1}元", intHour, intHour * 2);
        }
    }
}

配置文件:

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

小结:1、我们用反射技术+抽象工厂模式解决了收费方式的可维护和可扩展的问题,如果我们这个时候系统需要移植到第三个学校的机房,采取第三种收费方式,相关类的增加不可避免,注:系统中增加业务规则类不是模式所能解决的,无论采用什么设计模式。不过这叫扩展,开闭原则告诉我们,对于扩展我们开放,就目前而言,我们只需要修改配置文件中的参数即可。 2、我们看到客户端角色program只依赖抽象类,它根本不需要了解学校A和学校B的计费规则如何实现,program面对的只是计费规则的接口INormalCharge和IMemberCharge。所以,完全可以一个团队做计费规则,一个团队做计费规则的组装。两个团队都是相对接口编程,由抽象工厂来整合计费规则和客户端调用,这样便接触了模块间的耦合性。

 七、何时选用抽象工厂模式1、一个系统要独立于它的产品的创建、组合和表示时。2、一个系统要由多个产品系列中的一个来配置时。3、当你要强调一系列相关的产品对象的设计以便进行联合使用时。4、当你提供一个产品类库,而只想显示它们的接口而不是实现时。 

八、总结抽象工厂模式提供了一个创建一系列相关或相互依赖对象的接口,实现将客户和类的分离,客户通过他们的抽象接口操纵实例。主要用于应对"多系列对象"的需要变化,例如,同一个对象与多个数据库ORM多用抽象工厂模式来实现。通过学习抽象工厂模式,可以了解如何"面向接口"编程。