抽象工厂模式

[TOC]

原理

示例

计算员工的工资(C#实现)

需求

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

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

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

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

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

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

我们现在要为此构建一个软件系统(代号叫Softo),满足中国企业的需求。

需求分析

奖金(Bonus)、个人所得税(Tax)的计算是Softo系统的业务规则(Service)。

工资的计算(Calculator)则调用业务规则(Service)来计算员工的实际工资。

工资的计算作为业务规则的前端(或者客户端Client)将提供给最终使用该系统的用户(财务人员)使用。

针对中国企业为系统建模

根据上面的分析,为Softo系统建模如下:

实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Program
{
static void Main(string[] args)
{
ChineseBonus bonus = new ChineseBonus();
double bonusValue = bonus.Calculate();

ChineseTax tax = new ChineseTax();
double taxValue = tax.Calculate();

double salary = Constant.BaseSalary + bonusValue - taxValue;

Console.WriteLine("Chinese Salary is:" + salary);
Console.ReadLine();
}
}

/// <summary>
/// 公用的常量
/// </summary>
public class Constant
{
public static double BaseSalary = 4000;
}

/// <summary>
/// 计算中国个人所得税
/// </summary>
public class ChineseTax
{
public double Calculate()
{
return (Constant.BaseSalary + (Constant.BaseSalary * 0.1)) * 0.4;
}
}

/// <summary>
/// 计算中国个人奖金
/// </summary>
public class ChineseBonus
{
public double Calculate()
{
return Constant.BaseSalary * 0.1;
}
}

运行程序,输入的结果如下:

Chinese Salary is:2640

针对美国企业为系统建模

为了拓展国际市场,我们要把该系统移植给美国公司使用。

美国企业的工资计算同样是: 员工的工资 = 基本工资 + 奖金 - 个人所得税。

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

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

  • 奖金 = 基本工资 * 15 %

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

根据前面为中国企业建模经验,我们仅仅将ChineseTaxChineseBonus修改为AmericanTaxAmericanBonus。 修改后的模型如下:

实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Program
{
static void Main(string[] args)
{
AmericanBonus bonus = new AmericanBonus();
double bonusValue = bonus.Calculate();

AmericanTax tax = new AmericanTax();
double taxValue = tax.Calculate();

double salary = Constant.BaseSalary + bonusValue - taxValue;

Console.WriteLine("American Salary is:" + salary);
Console.ReadLine();
}
}

/// <summary>
/// 公用的常量
/// </summary>
public class Constant
{
public static double BaseSalary = 4000;
}

/// <summary>
/// 计算美国个人所得税
/// </summary>
public class AmericanTax
{
public double Calculate()
{
return Constant.BaseSalary*0.05 + (Constant.BaseSalary * 0.15*0.25);
}
}

/// <summary>
/// 计算美国个人奖金
/// </summary>
public class AmericanBonus
{
public double Calculate()
{
return Constant.BaseSalary * 0.15;
}
}

运行程序,输入的结果如下:

American Salary is:4250

问题改进

我们针对中国和美国分别做了实现,即使整合在一起,卖给中国和美国的企业时,调用处的代码仍然需要改变,违反了开闭原则.

更为致命的问题是:我们需要将这个移植工作转包给一个叫Hippo的软件公司。 由于版权问题,我们并未提供Softo系统的源码给Hippo公司,导致实际上移植工作无法进行。

为此,我们考虑增加一个工具类(命名为Factory),代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
class Program
{
static void Main(string[] args)
{
Bonus bonus = new Factory().CreateBonus();
double bonusValue = bonus.Calculate();

Tax tax = new Factory().CreateTax();
double taxValue = tax.Calculate();

double salary = Constant.BaseSalary + bonusValue - taxValue;

Console.WriteLine("Chinese Salary is:" + salary);
Console.ReadLine();
}
}

/// <summary>
/// Factory类
/// </summary>
public class Factory
{
public Tax CreateTax()
{
return new ChineseTax();
}

public Bonus CreateBonus()
{
return new ChineseBonus();
}
}

/// <summary>
/// 个人所得税抽象类
/// </summary>
public abstract class Tax
{
public abstract double Calculate();
}

/// <summary>
/// 奖金抽象类
/// </summary>
public abstract class Bonus
{
public abstract double Calculate();
}

/// <summary>
/// 计算中国个人所得税
/// </summary>
public class ChineseTax : Tax
{
public override double Calculate()
{
return (Constant.BaseSalary + (Constant.BaseSalary * 0.1)) * 0.4;
}
}

/// <summary>
/// 计算中国个人奖金
/// </summary>
public class ChineseBonus : Bonus
{
public override double Calculate()
{
return Constant.BaseSalary * 0.1;
}
}

/// <summary>
/// 公用的常量
/// </summary>
public class Constant
{
public static double BaseSalary = 4000;
}

为系统增加抽象工厂方法

如果我们使用上面的工厂方法,移植时还是要增加AmericanTaxAmericanBonus类,Factory类也要作相应修改,工作量并没有任何缩减

从Factory类在系统移植时修改的内容我们可以看出: 实际上它是专属于美国企业或者中国企业的。名称上应该叫AmericanFactory,ChineseFactory更合适.

解决方案是增加一个抽象工厂类AbstractFactory,增加一个静态方法,该方法根据一个配置文件(App.config或者Web.config) 一个项(比如factoryName)动态地判断应该实例化哪个工厂类,这样,我们就把移植工作转移到了对配置文件的修改。修改后的模型如下:

AbstractFactory类代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/// <summary>
/// 抽象Factory类
/// </summary>
public abstract class AbstractFactory
{
public static AbstractFactory GetInstance(string factoryName)
{
AbstractFactory instance = null;

if (factoryName != "")
{
instance = (AbstractFactory)Assembly.GetExecutingAssembly().CreateInstance(factoryName);
}

return instance;
}


public abstract Tax CreateTax();

public abstract Bonus CreateBonus();
}

全部代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
public class Program
{
static void Main(string[] args)
{
var configuration = new ConfigurationBuilder()
.AddJsonFile("app.json", true, true).Build();

var factoryName = configuration["factoryName"];

var factory = AbstractFactory.GetInstance(factoryName);


var bonus = factory.CreateBonus();
var bonusValue = bonus.Calculate();

var tax = factory.CreateTax();
var taxValue = tax.Calculate();

double salary = Constant.BaseSalary + bonusValue - taxValue;

Console.WriteLine($"Salary is:{salary}");
}
}

/// <summary>
/// 抽象Factory类
/// </summary>
public abstract class AbstractFactory
{
public static AbstractFactory GetInstance(string factoryName)
{
AbstractFactory instance = null;

if (factoryName != "")
{
instance = (AbstractFactory)Assembly.GetExecutingAssembly().CreateInstance(factoryName);
}

return instance;
}


public abstract Tax CreateTax();

public abstract Bonus CreateBonus();
}

/// <summary>
/// AmericanFactory类
/// </summary>
public class AmericanFactory : AbstractFactory
{
public override Tax CreateTax()
{
return new AmericanTax();
}

public override Bonus CreateBonus()
{
return new AmericanBonus();
}
}

/// <summary>
/// ChineseFactory类
/// </summary>
public class ChineseFactory : AbstractFactory
{
public override Tax CreateTax()
{
return new ChineseTax();
}

public override Bonus CreateBonus()
{
return new ChineseBonus();
}
}

/// <summary>
/// 奖金抽象类
/// </summary>
public abstract class Bonus
{
public abstract double Calculate();
}

/// <summary>
/// 个人所得税抽象类
/// </summary>
public abstract class Tax
{
public abstract double Calculate();
}

/// <summary>
/// 计算中国个人奖金
/// </summary>
public class ChineseBonus : Bonus
{
public override double Calculate()
{
return Constant.BaseSalary * 0.1;
}
}

/// <summary>
/// 计算中国个人所得税
/// </summary>
public class ChineseTax : Tax
{
public override double Calculate()
{
return (Constant.BaseSalary + (Constant.BaseSalary * 0.1)) * 0.4;
}
}

/// <summary>
/// 计算美国奖金
/// </summary>
public class AmericanBonus : Bonus
{
public override double Calculate()
{
return Constant.BaseSalary * 0.15;
}
}

/// <summary>
/// 计算美国个人所得税
/// </summary>
public class AmericanTax : Tax
{
public override double Calculate()
{
return Constant.BaseSalary * 0.05 + (Constant.BaseSalary * 0.15 * 0.25);
}
}



public class Constant
{
public static double BaseSalary = 4000;
}

app.json内容如下:

1
2
3
{
"factoryName": "AbstractFactory.AmericanFactory"
}

修改配置文件的工作很简单,只要写一篇幅配置文档说明书提供给移植该系统的团队(比如Hippo公司) 就可以方便地切换使该系统运行在美国或中国企业。