抽象工厂模式

上一篇 / 下一篇  2009-08-26 15:59:43 / 个人分类:.NET设计模式

虚拟案例

中国企业需要一项简单的财务计算:每月月底,财务人员要计算员工的工资。

员工的工资= (基本工资+奖金-个人所得税)。这是一个放之四海皆准的运算法则。

为了简化系统,我们假设员工基本工资总是4000美金。

中国企业奖金和个人所得税的计算规则是:

               奖金=基本工资(4000) * 10%

               个人所得税= (基本工资+奖金) * 40%

 

于是我们新建一个文件ChineseSalary.cs,里面代码如下:

namespace DesignPattern.AbstractFactory.ChineseSalary
{
    public class Constant
    {
        public static double BASE_SALARY = 4000;
    }

    public class ChineseTax
    {
        public double Calculate()
        {
            return (Constant.BASE_SALARY + (Constant.BASE_SALARY * 0.1)) * 0.4;
        }
    }

    public class ChineseBounus
    {
        public double Calculate()
        {
            return Constant.BASE_SALARY * 0.1;
        }
    }
}

然后我们可以在客户端这样调用:

static void Main(string[] args)
        {
            ChineseBounus bounus = new ChineseBounus();
            ChineseTax tax = new ChineseTax();

            double salary = Constant.BASE_SALARY + bounus.Calculate() - tax.Calculate();

            Console.WriteLine(salary);
            Console.Read();
        }

 

现在,这个系统如果要移植到美国,美国企业的工资计算同样是:员工的工资=基本工资+奖金-个人所得税。

但是他们的奖金和个人所得税的计算规则不同于中国企业:

美国企业奖金和个人所得税的计算规则是:

       奖金=基本工资* 15 %

       个人所得税= (基本工资* 5% +奖金* 25%)  

我们发现不论是中国企业还是美国企业,他们的业务运规则都采用同样的计算接口。于是很自然地想到建立两个业务接口类TaxBonus,然后让AmericanTaxAmericanBonusChineseTaxChineseBonus分别实现这两个接口

我们首先定义这两个接口类

namespace DesignPattern.AbstractFactory
{
    public interface Tax
    {
        double Calculate();  -  这儿如果加上public是错误的,因为接口默认都是公共的
    }

    public interface Bounus
    {
        double Calculate();
    }
}

 

新建另一个文件,名字叫AmericanSalary.cs,代码如下

namespace DesignPattern.AbstractFactory.AmericanSalary
{
    public class Constant
    {
        public static double BASE_SALARY = 4000;
    }

    public class AmericanTax:Tax
    {
        #region Tax Members

        double Tax.Calculate()
        {
            return (Constant.BASE_SALARY + (Constant.BASE_SALARY * 0.1)) * 0.4;
        }

        #endregion
    }

    public class AmericanBounus:Bounus
    {
        #region Tax Members

        double Bounus.Calculate()
        {
            return Constant.BASE_SALARY * 0.15;
        }

        #endregion
    }
}

同理,ChineseBounus等也需要修改

然后在美国的客户端可以这样使用:

        static void Main(string[] args)
        {
            Bounus bounus = new AmericanBounus();
            Tax tax = new AmericanTax();

            double salary = Constant.BASE_SALARY + bounus.Calculate() - tax.Calculate();

            Console.WriteLine(salary);
            Console.Read();
        }

 

 

我们可以看到,当我们将系统从中国移植到美国的时候(换句话,我认为可以说是一系列相关的类 - Bounus类跟Tax类在功能上没有变化,但实现方式多样化的时候),我们要增加一个新的Bounus类AmericianBounus类个一个新的Tax类AmericianTax类;并且,我们要在客户端修改所有new这些类的地方。我们首先要做的就是在客户端不要直接调用AmericanBounus等类,因为这样会与他们耦合性太紧密,所以我们新建一个类,用来返回AmericanBounus等类的实例:

class AmericanFactory
    {
        public static Bounus CreateBounus()
        {
            return new AmericanBounus();
        }

        public static Tax CreateTax()
        {
            return new AmericanTax();
        }
    }

    public static class ChineseFactory
    {
        public static Bounus CreateBounus()
        {
            return new ChineseBounus();
        }

        public static Tax CreateTax()
        {
            return new ChineseTax();
        }
    }

这样,如果在中国使用,我们可以在客户端这样写:

static void Main(string[] args)
        {
            Bounus bounus = new ChineseFactory().CreateBounus();
            Tax tax =new  ChineseFactory().CreateTax();

            double salary = Constant.BASE_SALARY + bounus.Calculate() - tax.Calculate();

            Console.WriteLine(salary);
            Console.Read();
        }

如果在美国使用,只要也调用相应的工厂类就可以了。

现在考虑,如果要增加日本的计算类呢,我们需要新增JapaneseBounus类,以及相应的工厂方法类JapaneseFactory,也就是说我们还要修改客户端中调用ChineseFactory的地方,就是说,我们虽然与Bounus和Tax类的耦合性降低了,但仍然与创建他们的工厂类有耦合性,又因为在新建一个Bounus类和Tax类的时候,我们必须为其创建一个工厂类,所以就是说在每次增加一个新的Bounus和Tax类的时候,我们也要修改客户端对相应的工厂类的调用,所以我们考虑继续增加一个抽象的工厂,让其他的工厂都继承子这个抽象工厂:

public abstract class AbstractFactory
    {
        public AbstractFactory GetInstance()
        {
            if(..)
            {
                return new ChineseFactory();
            }
            else if(..)
            {
                return new AmericanFactory();
            }
            ...
        }
        public abstract Bounus CreateBounus();  -  从这里也可以看出抽象工厂模式的使用情形,就是已经知道使用
        public abstract Tax CreateTax();                 - 哪些类了,只是这些类的实现方式会多样化。或者说,这个工厂
    }                                                                           - 生产的东西是知道的,只是每样东西样式不一样。

 

这样的好处是,我们可以通过读配置文件来让抽象工厂的GetInstance()方法来决定到底返回哪种类型的具体工厂,当然就是读写配置文件,在新增一个工厂的时候也是要修改If那地方的语句,所以,另外一种方式就是我们可以读完配置文件等之后,用反射的方式来加载新的类,而不是用If..Else。

 

优点

l        分离了具体的类。抽象工厂模式帮助你控制一个应用创建的对象的类,因为一个工厂封装创建产品对象的责任和过程。它将客户和类的实现分离,客户通过他们的抽象接口操纵实例,产品的类名也在具体工厂的实现中被分离,它们不出现在客户代码中。

l        它使得易于交换产品系列。一个具体工厂类在一个应用中仅出现一次——即在它初始化的时候。这使得改变一个应用的具体工厂变得很容易。它只需改变具体的工厂即可使用不同的产品配置,这是因为一个抽象工厂创建了一个完整的产品系列,所以整个产品系列会立刻改变。

l        它有利于产品的一致性。当一个系列的产品对象被设计成一起工作时,一个应用一次只能使用同一个系列中的对象,这一点很重要,而抽象工厂很容易实现这一点。

缺点

l        难以支持新种类的产品。难以扩展抽象工厂以生产新种类的产品。这是因为抽象工厂几口确定了可以被创建的产品集合,支持新种类的产品就需要扩展该工厂接口,这将涉及抽象工厂类及其所有子类的改变。

适用性

在以下情况下应当考虑使用抽象工厂模式:

l        一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有形态的工厂模式都是重要的。

l        这个系统有多于一个的产品族,而系统只消费其中某一产品族。

l        同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。

l        系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于实现。

应用场景

l        支持多种观感标准的用户界面工具箱(Kit)。

l        游戏开发中的多风格系列场景,比如道路,房屋,管道等。

l        ……

总结

总之,抽象工厂模式提供了一个创建一系列相关或相互依赖对象的接口,运用抽象工厂模式的关键点在于应对“多系列对象创建”的需求变化。一句话,学会了抽象工厂模式,你将理解OOP的精华:面向接口编程。

 


TAG:

 

评分:0

我来说两句

Open Toolbar