• 11 C++设计模式之享元(Flyweight)模式


    享元模式定义

    享元是一种结构型设计模式,它摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,让你能在有限的内存容量中载入更多对象。

    享元模式优缺点

    优点

    • 如果程序中有很多相似对象,那么你将可以节省大量内存。

    缺点

    • 使用享元模式需要维护一个记录了系统已有的所有享元的列表,而这本身需要耗费资源。
    • 享元模式使得系统更加的复杂,为了使对象可以共享,需要将一些状态外部化,这是得程序的逻辑复杂化。因此,应当在有足够多的对象实例可供共享时才值得使用享元模式。

    享元模式构成与实现

    构成

    • 享元模式只是一种优化。在应用该模式之前,你要确定程序中存在与大量类似对象同时占用内存相关的内存消耗问题,并且确保该问题无法使用其他更好的方式来解决。
    • 享元(Flyweight)类包含原始对象中部分能在多个对象中共享的状态。同一享元对象可在许多不同情景中使用。享元中存储的状态被称为“内在状态”。传递给享元方法的状态被称为“外在状态”。
    • 情景(Context)类包含原始对象中各不相同的外在状态。情景与享元对象组合在一起就能表示原始对象的全部状态。
    • 通常情况下,原始对象的行为会保留在享元类中。因此调用享元方法必须提供部分外在状态作为参数。但你也可将行为移动到情景类中,然后将连入的享元作为单纯的数据对象。
    • 客户端(Client)负责计算或存储享元的外在状态。在客户端看来,享元是一种可在运行时进行配置的模板对象,具体的配置方式为向其方法中传入一些情景数据参数。
    • 享元工厂(Flyweight Factory)会对已有享元的缓存池进行管理。有了工厂后,客户端就无需直接创建享元,它们只需调用工厂并向其传递目标享元的一些内在状态即可。工厂会根据参数在之前已创建的享元中进行查找,如果找到满足条件的享元就将其返回;如果没有找到就根据参数新建享元。

    实例

    Flyweight.h:

    #ifndef FLYWEIGHT_H_
    #define FLYWEIGHT_H_
    
    #include 
    
    // 享元类包含了树类型的部分状态, 这些成员变量保存的数值对于特定树而言是唯一的。
    // 很多树木之间包含共同的名字、颜色和纹理, 如果在每棵树中都存储这些数据就会浪费大量内存。
    // 因此我们将这些「内在状态」导出到一个单独的对象中, 然后让众多的单个树对象去引用它。
    class TreeType {
     public:
        TreeType(std::string n, std::string c, std::string t) :
            name_(n), color_(c), texture_(t) {}
        void draw(std::string canvas, double x, double y) {
            // 1. 创建特定类型、颜色和纹理的位图
            // 2. 在画布坐标(x,y)处绘制位图
            return;
        }
    
     private:
        std::string name_;
        std::string color_;
        std::string texture_;
    };
    
    #endif  // FLYWEIGHT_H_
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    Context.h:

    #ifndef CONTEXT_H_
    #define CONTEXT_H_
    
    #include 
    #include "Flyweight.h"
    
    // 情景对象包含树类型的「外在状态」, 程序中可以创建数十亿个此类对象, 因为它们体积很小: 仅有两个浮点坐标类型和一个引用成员变量
    class Tree {
     public:
        Tree(double x, double y, TreeType* t) : x_(x), y_(y), type_(t) {}
        void draw(std::string canvas) {
            return type_->draw(canvas, x_, y_);
        }
    
     private:
        double x_;
        double y_;
        TreeType* type_;
    };
    
    #endif  // CONTEXT_H_
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    FlyweightFactory.h

    #ifndef FLYWEIGHT_FACTORY_H_
    #define FLYWEIGHT_FACTORY_H_
    
    #include 
    #include 
    #include 
    #include "Flyweight.h"
    
    // 享元工厂: 决定是否复用已有享元或者创建一个新的对象, 同时它也是一个单例模式
    class TreeFactory {
     public:
        static TreeFactory* getInstance() {
            if (instance_ == nullptr) {
                mutex_.lock();
                if (instance_ == nullptr) {
                    instance_ = new TreeFactory();
                }
                mutex_.unlock();
            }
            return instance_;
        }
        TreeType* getTreeType(std::string name, std::string color, std::string texture) {
            std::string key = name + "_" + color + "_" + texture;
            auto iter = tree_types_.find(key);
            if (iter == tree_types_.end()) {
                // 新的tree type
                TreeType* new_tree_type = new TreeType(name, color, texture);
                tree_types_[key] = new_tree_type;
                return new_tree_type;
            } else {
                // 已存在的tree type
                return iter->second;
            }
        }
    
     private:
        TreeFactory() {}
        static TreeFactory* instance_;
        static std::mutex mutex_;
    
        // 共享池, 其中key格式为name_color_texture
        std::map<std::string, TreeType*> tree_types_;
    };
    
    #endif  // FLYWEIGHT_FACTORY_H_
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    FlyweightFactory.cpp

    #include "FlyweightFactory.h"
    
    TreeFactory* TreeFactory::instance_ = nullptr;
    std::mutex TreeFactory::mutex_;
    
    • 1
    • 2
    • 3
    • 4

    Client.h:

    #ifndef CLIENT_H_
    #define CLIENT_H_
    
    #include 
    #include 
    #include 
    #include "FlyweightFactory.h"
    #include "Context.h"
    
    // Forest包含数量及其庞大的Tree
    class Forest {
     public:
        void planTree(double x, double y, std::string name, std::string color, std::string texture) {
            TreeType* type = TreeFactory::getInstance()->getTreeType(name, color, texture);
            Tree tree = Tree(x, y, type);
            trees_.push_back(tree);
        }
        void draw() {
            for (auto tree : trees_) {
                tree.draw("canvas");
            }
        }
    
     private:
        std::vector<Tree> trees_;
    };
    
    #endif  // CLIENT_H_
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    main.cpp

    #include "Client.h"
    
    int main() {
    
        system("chcp 65001");
        Forest* forest = new Forest();
    
        // 在forest中种植很多棵树
        for (int i = 0; i < 500; i++) {
            for (int j = 0; j < 500; j++) {
                double x = i;
                double y = j;
                // 树类型1: 红色的杉树
                forest->planTree(x, y, "杉树", "红色", "");
                // 树类型2: 绿色的榕树
                forest->planTree(x, y, "榕树", "绿色", "");
                // 树类型3: 白色的桦树
                forest->planTree(x, y, "桦树", "白色", "");
            }
        }
    
        forest->draw();
        system("pause");
    
        delete forest;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
  • 相关阅读:
    Flink的基于两阶段提交协议的事务数据汇实现
    软件泥潭真体验
    [问题已处理]-虚拟机报错contains a file system with errors check forced
    【十二】图解mybatis日志模块之设计模式
    QAbstractScrollArea、QScrollArea
    什么是美颜SDK?为什么开发者应该考虑集成直播美颜SDK?
    域名一定需要备案吗?什么情况下不用备案?
    CentOS7如何设置开机自启动程序、开机自启动脚本?
    通过ExecutorService、Callable、Future实现有返回结果的多线程来处理有轮询业务
    hive-学习搜狗日志分析
  • 原文地址:https://blog.csdn.net/qq_45531502/article/details/125618969