设计模式中,创建型模式是最常用的,也是最基础的设计模式。理解这些,对于理解别的复杂的模式有一定的帮助。一般来说,讲创建型模式都先会讲单件模式,这里,为了讲述方便,我先从工厂方法讲起。 下面先从一个案例分析开始这一讲吧:
中国企业需要一项简单的财务计算:每月月底,财务人员要计算员工的工资。
员工的工资 = (基本工资 + 奖金 - 个人所得税)。
中国企业奖金和个人所得税的计算规则是:
奖金 = 基本工资(4000) * 10%
个人所得税 = (基本工资 + 奖金) * 40%
美国企业的工资计算同样是: 员工的工资 = 基本工资 + 奖金 - 个人所得税。
但是他们的奖金和个人所得税的计算规则不同于中国企业:
美国企业奖金和个人所得税的计算规则是:
奖金 = 基本工资 * 15 %
个人所得税 = (基本工资 * 5% + 奖金 * 25%)
(注:以上案例来自
TerryLee 设计模式系例文章《
NET设计模式(3):抽象工厂模式(Abstract Factory)》中的案例。)
在这里,假设我们需要为某一个跨国公司在美国和中国的企业设计这样一个员工工资计算系统。当然我们可以分别为美国的公司和中国的公司量身定做二套系统,但明显这种做法不明智的,浪费人力物力不说,碰到系统升级等行为,我们不得不为此而付出更多的代价。
我们可以看到,不管是在美国的企业还是在中国的企业,我们要解决的都是同一个问题,那就是员工工资的计算,唯一不同的就是中国企业和美国企业奖金和个人所得税的计算方法不一样而已,这样的话,我们完全可以只设计一个软件系统,而根据不同的国家采取不同的部署即可以解决。
在这里我们尽可能把这套系统想的最简单,因为我们不是要了解这个系统复杂的业务,而是在这个简单的系统里来运用设计模式解决方案,这样更明显一点,也更好理解一点,当然在这个杂谈的例子里,主要是为了讲述设计模式的用法,可能会导致设计过度,这个在这里不作讨论了,呵呵,这些在讲之前先声明一下,免得各位朋友误解。
好了言归正传,既然我们决定只开发一个软件系统,接下来就是怎么来设计了。如果抛开面向对象方法的话,用面向过程的,那是最简单不过了,用IF、ELSE等判断一下,当前要用于哪一个国家公司的员工工资计算,再根据上面的计算方法就可以算出员工工资了,这些在这里就不再详细说明了,在
TerryLee 设计模式系例文章《
NET设计模式(3):抽象工厂模式(Abstract Factory)》中已经讲的很详细了,我们就直接切入正题。
工厂方法模式,主要是用来解决某一个单一对象的创建问题。把变化部分封装在单独的对象里,然后在实际应用时,根据不同的情况来获取不同的对象,从而满足解决方案的需求,注意工厂方法是解决某个对象的创建问题的。根据这个我们再来看这个案例,对于程序的主干部分,我想比较明确了,就是要计算员工工资,至于计算工资的方法,则根据不同的国家有不同的规则,也就是说计算工资具体实现是变化的。好,既然我们已经找到了变化点在哪里,我们就把每个国家的计算工资实现封装在一个类里,当然,不管是美国公司还是中国公司,都只要实现一个计算工资方法即可,这样我们可以抽象出一个计算工资的接口来,代码如下:
1
using
System;
2
using
System.Collections.Generic;
3
using
System.Text;
4
5
namespace
DesignPattern.IBLL
6
{
7
public
interface
ISalary
8
{
9
double
Calculate(
double
basicSalary);
10
}
11
}
12
接下来,我们定义分别对于美国公司和中国公司的具体实现类:
中国公司:
1
using
System;
2
using
System.Collections.Generic;
3
using
System.Text;
4
5
namespace
DesignPattern.BLL
6
{
7
public
class
ChineseSalary : IBLL.ISalary
8
{
9
public
double
Calculate(
double
basicSalary)
10
{
11
double
bonus;
12
double
tax;
13
double
salary;
14
15
bonus
=
basicSalary
*
0.1
;
//
奖金计算
16
tax
=
(basicSalary
+
bonus)
*
0.4
;
//
个人所得税计算
17
18
salary
=
basicSalary
+
bonus
-
tax;
//
最终所得工资计算
19
20
return
salary;
21
}
22
}
23
}
24
美国公司:
1
using
System;
2
using
System.Collections.Generic;
3
using
System.Text;
4
5
namespace
DesignPattern.BLL
6
{
7
public
class
AmericanSalary : IBLL.ISalary
8
{
9
public
double
Calculate(
double
basicSalary)
10
{
11
double
bonus;
12
double
tax;
13
double
salary;
14
15
bonus
=
basicSalary
*
0.15
;
//
奖金计算
16
tax
=
basicSalary
*
0.05
+
bonus
*
0.25
;
//
个人所得税计算
17
18
salary
=
basicSalary
+
bonus
-
tax;
//
最终所得工资计算
19
20
return
salary;
21
}
22
}
23
}
24
好计算工资具体的实现类已经完成,接下来,我们就要创建专门的工厂,用它来创建实际用到的计算工资的对象。每个工厂要做的就是创建自己对应的计算工资方法的对象,同样我们可以抽象出一个接口,代码如下:
1
using
System;
2
using
System.Collections.Generic;
3
using
System.Text;
4
5
namespace
DesignPattern.IBLL
6
{
7
public
interface
IFactory
8
{
9
ISalary CreateSalary();
10
}
11
}
12
注意,这里CrateSalary方法返回的是ISalary接口,这个就是在开始篇里讲到的其中一个原则,要依赖于抽象,不要依赖于实现细节。
接下来,分别创建对应于美国企业和中国企业的具体的工厂方法:
中国企业:
1
using
System;
2
using
System.Collections.Generic;
3
using
System.Text;
4
5
namespace
DesignPattern.Factory
6
{
7
public
class
ChineseFactory : IBLL.IFactory
8
{
9
public
IBLL.ISalary CreateSalary()
10
{
11
return
new
BLL.ChineseSalary();
12
}
13
14
}
15
}
16
美国企业:
1
using
System;
2
using
System.Collections.Generic;
3
using
System.Text;
4
5
namespace
DesignPattern.Factory
6
{
7
public
class
AmericanFactory : IBLL.IFactory
8
{
9
public
IBLL.ISalary CreateSalary()
10
{
11
return
new
BLL.AmericanSalary();
12
}
13
14
}
15
}
16
到此为止,按照我们的当初设计,分离出变化点,封装在单独的对象里,再创建工厂,利用不同的工厂方法创建相应的对象,已经完成。在这里可能会有朋友会说,何必再多此一举创建工厂方法呢,直接根据不同的情况初始化不同的实现对象不就可以了,确实,如果单单要实现这个功能的话确实足够了,但在实际的应用中,这些对象可能在各个模块中会被频繁的创建,这样的话,如果要在美国公司和中国公司分别部署的话,则比较困难,而且很难适应将来的变化,而用工厂方法的话,我们则可以把具体工厂方法的创建统一来管理,在需要用到的模块里再根据该工厂方法创建相应的对象即可,这样的话,一旦要改变部署的话,我们只要改变具体工厂方法的创建即可,因为这个是统一来管理的,则可以把改变降到最低,当然我在下面的几篇里会讲到更好的方法来应对这种变化。
下面我们就来看看最后的实现吧:
1
double
basicSalary;
2
double
result;
3
4
basicSalary
=
Convert.ToDouble(txtBasicSalary.Text.Trim());
5
6
IBLL.IFactory factory
=
new
Factory.ChineseFactory();
7
IBLL.ISalary salary
=
factory.CreateSalary();
8
9
result
=
salary.Calculate(basicSalary);
10
11
lbSalary.Text
=
result.ToString();
如果要在美国公司用,则只要改变一个语句:
把
1
IBLL.IFactory factory
=
new
Factory.ChineseFactory();
改成:
1
IBLL.IFactory factory
=
new
Factory.AmericanFactory();
即可。
好了,工厂方法就讲到这里了,这里,我们看到,还是需要改变代码来实现不同的部署,这里可以有更好的方法来实现,我们只需要改变配置文件,不必修改代码就可以实现,这一点我就在下一篇中再改进。由于个人的理解有限,有些原理性的东西也讲不大清楚,这一点希望各位朋友能够一起讨论,一起进步。
工厂方法例子源码下载
下一篇:
设计模式杂谈:创建型模式之单件模式(Singleton)