一. 程序设计目标
我们组写了个简单的水果生产程序,描述农场种植水果的过程,旨在通过此次设计更进一步了解工程设计模式,加强编程的结构化能力。
开发环境:JDK1.5
开发工具:JBuilder 2006
二.程序设计介绍
1.程序结构
我们组为一个水果公司写了个简单的生产程序,该公司专门向市场销售各类水果。我们为程序建立了一个名为farm的工程,程序结构比较简单,总共有7个类,并且都放在一个默认的包中。其层次结构可从下图体现出来:
对各个类的说明:
Fruit类:水果接口,实现水果方法
Apple类:苹果类,实现Fruit接口
Grape类:葡萄类,实现Fruit接口
Strawberry类:草莓类,实现Fruit接口
FruitGardener类:园丁类,可种植各种水果
BadFruitException类:要种植的水果不在公司经营的水果范围之内,抛出种植异常
PlantFruit类:实现main()方法
2.程序设计步骤
在这个系统里需要描述下列的水果:
葡萄 Grape
草莓 Strawberry
苹果 Apple
水果与其他的植物有很大的不同,就是水果最终是可以采摘食用的。那么一个自然的
作法就是建立一个各种水果都适用的接口,以便与农场里的其他植物区分开。如下图所示。
水果接口规定出所有的水果必须实现的接口,包括任何水果类必须具备的方法:种植plant(),生长grow()以及收获harvest()。接口Fruit 的类图如下所示。
这个水果接口的源代码如下所示。
代码清单1:接口Fruit 的源代码
public interface Fruit {
// 生长
void grow();
//收获
void harvest();
//种植
void plant();
}描述苹果的Apple 类的源代码的类图如下所示。
Apple 类是水果类的一种,因此它实现了水果接口所声明的所有方法。另外,由于苹果是多年生植物,因此多出一个treeAge 性质,描述苹果树的树龄。下面是这个苹果类的源代码。
代码清单2:类Apple 的源代码
public class Apple
implements Fruit {
private int treeAge;
//生长
public void grow() {
log("Apple is growing...");
}
// 收获
public void harvest() {
log("Apple has been harvested.");
}
//种植
public void plant() {
log("Apple has been planted.");
}
// 辅助方法
public static void log(String msg) {
System.out.println(msg);
}
//树龄的取值方法
public int getTreeAge() {
return treeAge;
}
// 树龄的赋值方法
public void setTreeAge(int treeAge) {
this.treeAge = treeAge;
}
}
同样,Grape 类是水果类的一种,也实现了Fruit 接口所声明的所有的方法。但由于葡萄分有籽和无籽两种,因此,比通常的水果多出一个seedless 性质,如下图所示。
葡萄类的源代码如下所示。可以看出,Grape 类同样实现了水果接口,从而是水果类型的一种子类型。
代码清单3:类Grape 的源代码
public class Grape
implements Fruit {
private boolean seedless;
//生长
public void grow() {
log("Grape is growing...");
}
//收获
public void harvest() {
log("Grape has been harvested.");
}
//种植
public void plant() {
log("Grape has been planted.");
}
//辅助方法
public static void log(String msg) {
System.out.println(msg);
}
// 有无籽的取值方法
public boolean getSeedless() {
return seedless;
}
//有无籽的赋值方法
public void setSeedless(boolean seedless) {
this.seedless = seedless;
}
}
下图所示是Strawberry 类的类图。
Strawberry 类实现了Fruit 接口,因此,也是水果类型的子类型,其源代码如下所示。
代码清单4:类Strawberry 的源代码
public class Strawberry
implements Fruit {
//生长
public void grow() {
log("Strawberry is growing...");
}
//收获
public void harvest() {
log("Strawberry has been harvested.");
}
//种植
public void plant() {
log("Strawberry has been planted.");
}
//辅助方法
public static void log(String msg) {
System.out.println(msg);
}
}
农场的园丁也是系统的一部分,自然要由一个合适的类来代表。这个类就FruitGardener 类,其结构由下面的类图描述。
FruitGardener 类会根据客户端的要求,创建出不同的水果对象,比如苹果(Apple),葡萄(Grape)或草莓(Strawberry)的实例。而如果接到不合法的要求,FruitGardener 类会抛出BadFruitException 异常,如下图所示。
园丁类的源代码如下所示。
代码清单5:FruitGardener 类的源代码
public class FruitGardener {
//静态工厂方法
public static Fruit factory(String which) throws BadFruitException {
if (which.equalsIgnoreCase("apple")) {
return new Apple();
}
else if (which.equalsIgnoreCase("strawberry")) {
return new Strawberry();
}
else if (which.equalsIgnoreCase("grape")) {
return new Grape();
}
else {
throw new BadFruitException("Bad fruit request");
}
}
}
可以看出,园丁类提供了一个静态工厂方法。在客户端的调用下,这个方法创建客户端所需要的水果对象。如果客户端的请求是系统所不支持的,工厂方法就会抛出一个BadFruitException 异常。这个异常类的源代码如下所示。
代码清单6:BadFruitException 类的源代码
public class BadFruitException
extends Exception {
public BadFruitException(String msg) {
super(msg);
}
}
在使用时,客户端只需调用FruitGardener 的静态方法factory()即可。请见下面的示意性客户端源代码。
代码清单7:实现种植即Main()的实现
public class PlantFruit {
public PlantFruit() {
}
public static void main(String[] args) {
PlantFruit plantfruit = new PlantFruit();
try {
//种植葡萄
FruitGardener.factory("grape").plant();
FruitGardener.factory("grape").grow();
FruitGardener.factory("grape").harvest();
System.out.println("==================================");
//种植苹果
FruitGardener.factory("apple").plant();
FruitGardener.factory("apple").grow();
FruitGardener.factory("apple").harvest();
System.out.println("==================================");
//种植草莓
FruitGardener.factory("strawberry").plant();
FruitGardener.factory("strawberry").grow();
FruitGardener.factory("strawberry").harvest();
System.out.println("==================================");
}
catch (BadFruitException e) {
}
}
}
到此为止,我们的简单程序已经设计完成,我们可以通过创建FruitGardener对象来完成水果的种植,无论你要种什么,只需调用对象中的factory()方法。输出结果如下:
三.简单工厂模式的定义
简单工厂模式是类的创建模式,又叫做静态工厂方法(Static Factory Method)模式。简单工厂模式是由一个工厂对象决定创建出那一种产品类的实例。
四.简单工厂模式的结构
简单工厂模式是类的创建模式,这个模式的一般性结构如下图所示。
角色与结构
简单工厂模式就是由一个工厂类可以根据传入的参量决定创建出哪一种产品类的实例。下图所示为以一个示意性的实现为例说明简单工厂模式的结构。
从上图可以看出,简单工厂模式涉及到工厂角色、抽象产品角色以及具体产品角色等
三个角色:
(1)工厂类(Creator)角色:担任这个角色的是工厂方法模式的核心,含有与应用紧
密相关的商业逻辑。工厂类在客户端的直接调用下创建产品对象,它往往由一个
具体Java 类实现。
(2)抽象产品(Product)角色:担任这个角色的类是工厂方法模式所创建的对象的父
类,或它们共同拥有的接口。抽象产品角色可以用一个Java 接口或者Java 抽象类
实现。
(3)具体产品(Concrete Product)角色:工厂方法模式所创建的任何对象都是这个角
色的实例,具体产品角色由一个具体Java 类实现。
工厂类的示意性源代码如下所示。可以看出,这个工厂方法创建了一个新的具体产品的实例并返还给调用者。
代码清单8:Creator 类的源代码
public class Creator
{
//静态工厂方法
public static Product factory()
{
return new ConcreteProduct();
}
}
抽象产品角色的主要目的是给所有的具体产品类提供一个共同的类型,在最简单的情况下,可以简化为一个标识接口。所谓标识接口,就是没有声明任何方法的空接口。
代码清单9:抽象角色Product 接口的源代码
public interface Product
{
}
具体产品类的示意性源代码如下。
代码清单10:具体产品角色ConcreteProduct 类的源代码
public class ConcreteProduct implements Product
{
public ConcreteProduct(){}
}
虽然在这个简单的示意性实现里面只给出了一个具体产品类,但是在实际应用中一般都会遇到多个具体产品类的情况。
五.简单工厂模式的实现
1.多层次的产品结构
在真实的系统中,产品可以形成复杂的等级结构,比如下图所示的树状结构上就有多个抽象产品类和具体产品类。
这个时候,简单工厂模式采取的是以不变应万变的策略,一律使用同一个工厂类。如下图所示。
图中从Factory 类到各个Product 类的虚线代表创建(依赖)关系;从Client 到其他类的联线是一般依赖关系。这样做的好处是设计简单,产品类的等级结构不会反映到工厂类中来,从而产品类的等级结构的变化也就不会影响到工厂类。但是这样做的缺点是,增加新的产品必将导致工厂类的修改。
2. 使用Java 接口或者Java 抽象类
如果模式所产生的具体产品类彼此之间没有共同的商业逻辑,那么抽象产品角色可以由一个Java 接口扮演;相反,如果这些具体产品类彼此之间确有共同的商业逻辑,那么这些公有的逻辑就应当移到抽象角色里面,这就意味着抽象角色应当由一个抽象类扮演。在一个类型的等级结构里面,共同的代码应当尽量向上移动,以达到共享的目的,如下图所示。
六.模式的优点和缺点
1. 模式的优点
模式的核心是工厂类。这个类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例。而客户端则可以免除直接创建产品对象的责任,而仅仅负责“消费”产品。简单工厂模式通过这种做法实现了对责任的分割。
2. 模式的缺点
正如同在本章前面所讨论的,当产品类有复杂的多层次等级结构时,工厂类只有它自
己。以不变应万变,就是模式的缺点。这个工厂类集中了所有的产品创建逻辑,形成一个无所不知的全能类,有人把这种类叫做上帝类(God Class)。如果这个全能类代表的是农场的一个具体园丁的话,那么这个园丁就需要对所有的产品负责,成了农场的关键人物,他什么时候不能正常工作了,整个农场都要受到影响。将这么多的逻辑集中放在一个类里面的另外一个缺点是,当产品类有不同的接口种类时,工厂类需要判断在什么时候创建某种产品。这种对时机的判断和对哪一种具体产品的判断逻辑混合在一起,使得系统在将来进行功能扩展时较为困难。这一缺点在工厂方法模式中得到克服。
由于简单工厂模式使用静态方法作为工厂方法,而静态方法无法由子类继承,因此,工厂角色无法形成基于继承的等级结构。这一缺点会在工厂方法模式中得到克服。
七.个人体会
设计模式实际上是良好的OO思想的一种提炼。每一种设计模式后面都体现了一种良好的OO思路,这些思路对于解决软件中常见的“change”问题有很大的适应性,而每种模式又有自己独特的解决思路,带有一定的通用性。而组合各种模式又可以解决许多常见问题。不可否认的是,还存在一些未经总结的设计模式。实际上,你自己也可以总结一些模式出来。无论怎样,设计模式仍然是面向对象,它不是新东西,也没有必要言必称设计模式—似乎不懂设计模式就落伍了,但给OO的开发者提供一个言简意赅的沟通桥梁。
设计模式告诉了我们什么是好的OO思想,思考如何更好的应用OO的思想—虽然还是那几个耳熟能详的术语:封装、继承、组合、多态。
设计模式首先是对传统的OO使用方法的矫正:如针对接口编程而不是实现;优先使用组合,而不是继承。其次是在原来理解上的突破:封装是对变化而言的,不仅仅是属性和方法的集合。类不仅是现实事物的抽象,同时它还具有责任。更有创新:依赖式注入。
模式不是万能的,也并不总能完美地解决问题,因此每种模式都包括了影响的信息。在应用模式之前,我们必须先分析问题的情境,并评估模式的影响,再决定是否采用模式,采用哪一种模式。也就是说,理解、分析模式,和实现模式一样重要
八.建议
(1)使用更加通俗易懂的语言解释设计模式,并用完整的代码实例辅以说明。代码的演示时间应延长点,好让学生看清,看懂代码。
(2)在学生需要的时候给学生补充一点java知识,有些同学专注于其他语言,对于java也不太懂,听起课来一头雾水,这时候来点知识补充还是必要的。