简单工厂模式
Posted Leptus
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简单工厂模式相关的知识,希望对你有一定的参考价值。
一、动机
一个渲染器应用程序中存在着多种不同的相机(如透视相机、鱼眼相机等)。通过应用程序的图形界面,用户可以选择使用不同的相机来生成光线,对场景进行渲染。渲染程序如代码列表1所示。在代码中,我们使用字符串来表示相机类型,并使用if-else语句(6~10行代码)来实例化相应的Camera子类对象。然而,这种方式会存在着一些问题。首先,如果我们需要增加一个新的相机类型,或者删除一个已存在的相机类型,那么我们就需要修改这个if-else语句,这违背了开放-封闭原则。其次,这个if-else 语句可能存在于渲染器源代码的多个地方。那么,修改这个语句就意味着:我们需要在源程序中找出所有含有这个if-else语句的地方,然后进行修改。在这个过程中,也许是因为一两个包含这个if-else语句的地方没有被找出,也许是因为没有对源代码中的某个的if-else语句进行正确的修改,bug 往往就这样产生了。
代码列表1
1 void Render(int height, int width, const std::string& camera_type, Scene *scene) 2 { 3 assert(scene && height > 0 && width > 0); 4 assert(!cameraType.empty()); 5 6 Camera *camera = nullptr; 7 if (camera_type == "Perspective") 8 camera = new PerspectiveCamera(); 9 else if (cameraType == "Orthograph") 10 camera = new OrthographCamera(); 11 assert(camera); 12 13 Image image; 14 for (int i = 0; i < vHeight; ++ i) { 15 for (int j = 0; j < width; ++ j) { 16 Ray ray = camera->generateRay(i, j); 17 Color L = scene->intersect(ray); 18 image.addSample(i, j, L); 19 } 20 } 21 image.save(); 22 }
为了解决这些问题,我们可以将这段实例化$Camera$子类对象的代码抽取出来,放入一个函数中(如代码列表2所示)。这样就能够解决源代码中存在多份相同代码的问题。同时,$Render$函数也没有违背开放-封闭原则。当需要增加一个新的相机类型或者删除一个已经存在的相机类型时,我们只需要修改$CreateCamera$函数中的代码。虽然,在使用了这种方法后,$CreateCamera$函数违背了开放-封闭原则,但是不能否认的是,这种方法消除了原来代码中存在的一些问题。
代码列表2
Camera *CreateCamera(const std::string& camera_type) { assert(!camera_type.empty()); if (camera_type == "Perspective") return new PerspectiveCamera; if (camera_type == "Orthograph") return new OrthographCamera; assert(false); return nullptr; } void Render(int height, int width, const std::string& camera_type, Scene *scene) { assert(scene && height > 0 && width > 0); Camera *camera = CreateCamera(camera_type); Image image; for (int i = 0; i < vHeight; ++ i) { for (int j = 0; j < width; ++ j) { Ray ray = camera->generateRay(i, j); Color L = scene->intersect(ray); image.addSample(i, j, L); } } image.save(); }
"将实例化子类对象的代码放入函数中",这句话的深层含义是:将如何实例化子类对象的信息封装。通过这种封装,我们能够限制变化所能够影响的范围。在这个例子中,没有封装前,对于$Camera$子类的增加或者删除,我们都需要在源代码的多个地方进行修改;进行封装后,我们只需要修改$CreateCamera$函数。需要修改的地方越多,可能产生的bug也就越多。
二、面向对象编程中的做法
在进行面向对象编程时,人们通常都通过其他的方式来实现这种封装。在面向对象编程中,每个类都具有一个单一的职责,所以人们通常会使用一个类来专门负责实例化子类对象。这个类被称为$Simple Factory$。在面向对象编程中,虽然人们都使用一个类来负责实例化对象,然而,却存在着多种不同的做法。
- 一种做法是在$SimpleFactory$类中定义一个静态方法,通过这个静态方法来实例化子类对象(代码列表3)。
- 另一种做法是在$SimpleFactory$类中定义一个可以被$override$的方法(代码列表4)。
这两种做法存在着细微的差别。如果实例化子类对象的行为可能会改变,那么第二种方法是一个更好的方法。因为,我们可以通过继承的方式来改变实例化子类对象的行为。
代码列表3
class SimpleFactory { public: static Camera *CreateCamera(const std::string& camera_type) { assert(!camera_type.empty()); if (camera_type == "Perspective") return new PerspectiveCamera; if (camera_type == "Orthograph") return new OrthographCamera; assert(false); return nullptr; } }; void Render(int height, int width, const std::string& camera_type, Scene *scene) { assert(scene && height > 0 && width > 0); Camera *camera = SimpleFactory::CreateCamera(camera_type); Image image; for (int i = 0; i < vHeight; ++ i) { for (int j = 0; j < width; ++ j) { Ray ray = camera->generateRay(i, j); Color L = scene->intersect(ray); image.addSample(i, j, L); } } image.save(); }
代码列表4
class SimpleFactory { public: virtual Camera *CreateCamera(const std::string& camera_type) { assert(!camera_type.empty()); if (camera_type == "Perspective") return new PerspectiveCamera; if (camera_type == "Orthograph") return new OrthographCamera; assert(false); return nullptr; } }; void Render(int height, int width, const std::string& camera_type, Scene *scene, const SimpleFactory& factory) { assert(scene && height > 0 && width > 0); Camera *camera = factory.CreateCamera(camera_type); Image image; for (int i = 0; i < vHeight; ++ i) { for (int j = 0; j < width; ++ j) { Ray ray = camera->generateRay(i, j); Color L = scene->intersect(ray); image.addSample(i, j, L); } } image.save(); }
如果我们觉得实例化子类对象的行为在以后可能会改变,那么采用其他的方式,我们也能够达到与使用第二种方法相同的效果。由于函数对象的存在,我们可以将实例化子类对象的行为看做是一个函数对象,并将该函数对象作为$Render$函数的一个参数,通过传递给$Render$函数不同的参数,我们就能够使用不同的方式来实例化子类对象。在C++中,我们可以使用多种方式来实现这种想法,例如函数对象(代码列表5)或者使用模板(代码列表6)。
代码列表5
void Render(int height, int width, const std::string& camera_type, Scene *scene, const std::function<Camera*()>& creator) { assert(scene && height > 0 && width > 0); Camera *camera = creator(camera_type); Image image; for (int i = 0; i < vHeight; ++ i) { for (int j = 0; j < width; ++ j) { Ray ray = camera->generateRay(i, j); Color L = scene->intersect(ray); image.addSample(i, j, L); } } image.save(); }
代码列表6
template <typename Function> void Render(int height, int width, const std::string& camera_type, Scene *scene, const Function& creator) { assert(scene && height > 0 && width > 0); Camera *camera = creator(camera_type); Image image; for (int i = 0; i < vHeight; ++ i) { for (int j = 0; j < width; ++ j) { Ray ray = camera->generateRay(i, j); Color L = scene->intersect(ray); image.addSample(i, j, L); } } image.save(); }
三、动态语言的做法
在可以将类型作为参数的动态语言中,我们直接地改变传递给$Render$函数的类型参数从而达到相同的效果,如代码列表7所示。
代码列表7
class Camera: pass class PerspectiveCamera(Camera): pass class OrthgraphCamera(Camera): pass def Render(height, width, camera_type, scene): camera = camera_type() image = Image() for i in range(height): for j in range(width): ray = camera.generateRay(i, j) L = scene.intersect(ray) image.addSample(i, j, L) image.save() #client code Render(720, 1024, PerspectiveCamera, scene)
以上是关于简单工厂模式的主要内容,如果未能解决你的问题,请参考以下文章