抽象工厂模式的实践

2013年1月12日

抽象工厂模式,简单地说,由几个类构成:一个工厂类,一个或多个抽象类作为基类,多个具体类作为子类,还有一个接口。工厂类有一个工厂方法,它接收 一个参数,例如要创建对象的信息;返回一个实现了那个接口的对象。该对象的运行时类型则是上述子类中的一个。这些子类中的每一个都继承于那些基类中的一 个,那些基类再分别向上继承到一个共同的基类,它是继承关系的根。根类会实现那个接口。

模板方法

以Cat和Dog两个类为例,它们可以继承于Pet类,并实现接口IMyPet,然后,工厂类PetFactory的CreatePet方法能这样 工作:CreatePet(“Kittie”) 返回一个新创建的Cat对象,而CreatePet(“Woofy”) 返回一个Dog对象。它可以是根据数据库里有Kittie和Woofy的记录而去创建这些不同种类的对象的。

现在,我想让这个创建出来的宠物去喝牛奶。不过,为了考验它们,在它们的必经之路上有一个游泳池。猫不会游泳,我特意为它准备了一个竹筏(嗯,我相信她能自己学会怎样使用它)。然后,我分别调用每个宠物的GoToDrinkMilk()方法。该方法类似下面这样:

// in class Pet
public void GoToDrinkMilk()
{
    GetOutOfHouse();
    PassSwimmingPool();
    GoToTheBowl();
    DrinkMilk();
}


这个方法实现在基类Pet中,目的是把一些固定的步骤放在一个共享的方法里。这也就是模板方法。它里面调用的四个方法只要有必要就可以在子类中重写。我看,至少PassSwimmingPool是需要在子类中重写的,这样能让猫和狗以不同的方式通过游泳池。

// in class Pet
// override this in a child class
public abstract void PassSwimmingPool();
// in class Cat
public override void PassSwimmingPool()
{
    Boat b;
    b = FetchBoat();
    PutIntoWater(b);
    GetOntoBoat(b);
    PassRiver();
    PutBoatAtBank(b);
}
// in class Dog
public override void PassSwimmingPool()
{
    SwimToTheOtherSide();
}


有了这些实现之后,小动物们就能欢快地喝牛奶了。请注意,我们这里用了一个模板方法,也就是GoToDrinkMilk()。如果要让某些其他操作 能被共享,我们可以把更多的方法放在基类中实现。事实上,PassSwimmingPool中调用的每个方法,只要有必要,都可以在基类中实现。这样的 话,狗狗也能用竹筏了。甚至,可以把Cat类的PassSwimmingPool里的内容抽出来,放到基类中的一个叫 PassSwimmingPoolByBoat的方法中,这样如果再有另一种特殊类型的猫,它在其他方面与Cat不同,但过游泳池的行为却一样,那么它也 可以使用这个方法了。

工厂方法

前面讲了哪些方法可以作为模板方法放在基类中并多态地调用子类方法,也讲了怎样把一些方法的内容“下推”到基类中以便让多个子类能够共享,现在讲讲工厂方法的简单实现。

最简单的例子就是对象的数据是保存在数据库中的。比如有下表:

列名 类型 注释
PetId GUID 宠物的ID
PetName varchar 宠物的名字
PetType varchar 宠物的类型码,如CAT、DOG等
有关宠物的其他信息

对于这个表,如果有两条记录,其中一条的PetName是Kittie,PetType是CAT;另一条的PetName是 Woofy,PetType是DOG,那么当调用CreatePet(“Kittie”)时,它会去查询PetName为Kittie的那条记录,然后检 查PetType值,发现是CAT后,就创建一个Cat类的对象,并用数据库中的数据去初始化它。基本的工作方式就是这样。

需要注意的是,由于潜在地有些方法需要被“下推”到基类中,基类最好能包含绝大多数它可能用到的数据成员,包括那些在只有在特定的子类中才有的行 为。因为这些行为可能被多个子类共享而需要被“下推”到基类中,所以这些行为中要用到的数据成员最好也放在基类中作为受保护(protected)成员来 处理。这样会让程序修改的代价更低一些。

总结

本文讲述了编程实践中对抽象工厂设计模式的一些常见用法,包括基类与子类、接口、工厂方法及注意事项等。有了这些了解之后,您在将来的实践中或许能更容易地对这种场合进行编程。欢迎在r_mosaic的CSDN博客上提出您的意见和想法。

留下您的评论