虚拟案例
中国企业需要一项简单的财务计算:每月月底,财务人员要计算员工的工资。
员工的工资= (基本工资+奖金-个人所得税)。这是一个放之四海皆准的运算法则。
为了简化系统,我们假设员工基本工资总是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%)
我们发现不论是中国企业还是美国企业,他们的业务运规则都采用同样的计算接口。于是很自然地想到建立两个业务接口类Tax,Bonus,然后让AmericanTax、AmericanBonus和ChineseTax、ChineseBonus分别实现这两个接口
我们首先定义这两个接口类
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的精华:面向接口编程。