简单工厂模式

 一、动机

  一个渲染器应用程序中存在着多种不同的相机(如透视相机、鱼眼相机等)。通过应用程序的图形界面,用户可以选择使用不同的相机来生成光线,对场景进行渲染。渲染程序如代码列表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 < Height; ++ 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 < Height; ++ 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 < Height; ++ 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 < Height; ++ 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 < Height; ++ 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 < Height; ++ 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)
posted @ 2017-09-21 19:51  Leptus  阅读(204)  评论(0编辑  收藏  举报