zoukankan      html  css  js  c++  java
  • 简单工厂模式

     一、动机

      一个渲染器应用程序中存在着多种不同的相机(如透视相机、鱼眼相机等)。通过应用程序的图形界面,用户可以选择使用不同的相机来生成光线,对场景进行渲染。渲染程序如代码列表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)
  • 相关阅读:
    文件上传工具类
    使用java 的api获取两个集合的交集、并集、差集
    如何判断指定Class是否是List的子类或者父类,是否是数组
    如何判断指定Class是否是基础数据类型或者是其包装类型
    OVS中的key解析
    OVS
    Neutron网络学习
    NIO_2
    以太网帧格式总结
    VMWare中桥接、NAT、Host-only
  • 原文地址:https://www.cnblogs.com/tallisHe/p/7570577.html
Copyright © 2011-2022 走看看