当前位置: 首页 > article >正文

C++软件设计模式之享元模式(FlyWeight)

享元(Flyweight)模式的动机与意图

动机

享元模式的主要动机是通过共享对象来减少内存使用,从而提高系统的性能。在某些情况下,系统中可能有大量细粒度的对象,这些对象具有共同的部分状态,而这些状态可以共享。如果不进行共享,大量对象会占用大量的内存资源,特别是在对象的数量非常大的时候。享元模式通过共享这些对象的共有部分状态,使得系统只需要保存一份共有的状态,从而显著减少内存占用。

意图

享元模式的意图是通过共享技术来减少内存使用,并提高对象的创建和管理效率。具体来说,享元模式将一个对象的内部状态(可以共享的)和外部状态(不能共享的)分开,使得一个对象可以被多次共享,而每次使用时只需传递外部状态给对象。

适用场合

享元模式适用于以下场合:

  1. 对象数量庞大

    • 当一个应用程序中需要创建大量的对象,而这些对象占用大量内存时,可以考虑使用享元模式。通过共享这些对象的共有部分状态,可以显著减少内存占用。
  2. 对象状态可以分离

    • 对象的状态可以被分为内部状态和外部状态,其中内部状态是可以共享的,而外部状态是随上下文变化的。享元模式通过共享内部状态,同时在使用时传递外部状态,来实现对象的高效管理。
  3. 对象的大多数状态可以被外部化

    • 如果一个对象的大多数状态可以被外部化(即不保存在对象内部),并且这些状态可以在使用对象时传入,那么这个对象就适合使用享元模式。
  4. 需要缓存对象

    • 当需要缓存对象以提高访问效率时,可以通过享元模式来实现对象的共享和缓存,从而提高系统的性能。

具体示例

假设我们正在开发一个文本编辑器,需要在文档中显示大量的字符。每个字符对象都包含字体、颜色、大小等属性,如果不进行共享,每个字符的这些属性将占用大量的内存。通过享元模式,我们可以共享字符的字体、颜色和大小等共有部分状态,而每个字符的外部状态(如位置)则在使用时传递给对象。

字符类(内部状态)
#include <iostream>
#include <map>
#include <string>

class Character {
private:
    char _value;
public:
    Character(char value) : _value(value) {}

    void display(const std::string& font, const std::string& color, int size) {
        std::cout << "显示字符: " << _value << ",字体: " << font << ",颜色: " << color << ",大小: " << size << std::endl;
    }
};

class CharacterFactory {
private:
    std::map<char, std::shared_ptr<Character>> _characters;
public:
    std::shared_ptr<Character> getCharacter(char value) {
        if (_characters.find(value) == _characters.end()) {
            std::shared_ptr<Character> newChar = std::make_shared<Character>(value);
            _characters[value] = newChar;
            return newChar;
        } else {
            return _characters[value];
        }
    }
};

文档类(外部状态)
class Document {
private:
    std::vector<std::shared_ptr<Character>> _characters;
    std::string _font;
    std::string _color;
    int _size;
public:
    Document(const std::string& font, const std::string& color, int size) : _font(font), _color(color), _size(size) {}

    void addCharacter(char value, CharacterFactory& factory) {
        std::shared_ptr<Character> character = factory.getCharacter(value);
        _characters.push_back(character);
    }

    void display() {
        for (auto& character : _characters) {
            character->display(_font, _color, _size);
        }
    }
};

客户端代码
int main() {
    CharacterFactory factory;

    Document doc1("Arial", "Red", 12);
    doc1.addCharacter('A', factory);
    doc1.addCharacter('B', factory);
    doc1.addCharacter('C', factory);

    Document doc2("Times New Roman", "Blue", 14);
    doc2.addCharacter('A', factory);
    doc2.addCharacter('B', factory);
    doc2.addCharacter('D', factory);

    doc1.display();
    doc2.display();

    return 0;
}

代码解释

  1. Character 类

    • 代表一个字符对象,包含字符的值 _value
    • display 方法用于显示字符,同时接受字体、颜色和大小等外部状态作为参数。
  2. CharacterFactory 类

    • 用于创建和管理字符对象的工厂类。
    • 通过 getCharacter 方法,工厂类可以返回一个已存在的字符对象(共享对象),或者创建一个新的字符对象并加入到缓存中。
  3. Document 类

    • 代表一个文档,包含字符的集合 _characters 以及字体、颜色和大小等外部状态。
    • addCharacter 方法用于向文档中添加字符,通过 CharacterFactory 获取字符对象。
    • display 方法用于显示文档中的所有字符,同时传递字体、颜色和大小等外部状态给字符对象。
  4. 客户端代码

    • 创建 CharacterFactory 对象。
    • 创建两个文档对象 doc1 和 doc2,并分别为它们添加字符。
    • 显示两个文档中的字符,每个字符对象的内部状态(值)是共享的,而外部状态(字体、颜色、大小)是在使用时传递的。

总结

享元模式的主要动机是通过共享对象来减少内存使用,提高系统的性能。其适用场合包括:

  • 对象数量庞大:系统中存在大量细粒度的对象,这些对象占用大量内存。
  • 对象状态可以分离:对象的状态可以被分为内部状态和外部状态,其中内部状态是可以共享的。
  • 对象的大多数状态可以被外部化:对象的大多数状态可以不在对象内部保存,而是在使用对象时传入。
  • 需要缓存对象:通过缓存对象来提高访问效率。

通过享元模式,可以有效地管理和共享对象的共有状态,从而减少内存占用,提高系统性能。希望这些解释能帮助你更好地理解享元模式的动机、意图及其适用场合。

享元模式的 UML 类图

享元模式的 UML 类图如下所示:

+------------------------------+          +-----------------------------+
|         FlyweightFactory     |          |        Flyweight             |
|------------------------------|          |-----------------------------|
| +getInstance(key: String):   |          | +intrinsicState: String      |
|   Flyweight                   |          | +operation(extrinsicState:  |
|                               |          |   String): void             |
| +flyweights: Map<String,       |          |                             |
|   Flyweight>                  |          +-----------------------------+
| +getInstance(key: String):     |          |          +-----------------+
|   Flyweight                    |          |          | ConcreteFlyweight |
| +addFlyweight(key: String,     |          |          |------------------|
|   flyweight: Flyweight): void  |          |          | +intrinsicState: String|
+------------------------------+          |          | +operation(extrinsicState: |
                                          |          |   String): void            |
                                          +-----------------------------+         |
                                                                         +---------+

UML 类图解释

  1. FlyweightFactory

    • 职责:负责创建和管理享元对象。
    • 方法
      • getInstance(key: String): Flyweight:根据给定的键返回一个享元对象。如果享元对象已经存在于缓存中,则返回缓存中的对象;否则,创建一个新的享元对象并将其加入到缓存中,然后返回。
      • addFlyweight(key: String, flyweight: Flyweight): void:将新的享元对象添加到缓存中。
    • 属性
      • flyweights: Map<String, Flyweight>:存储享元对象的缓存,键通常是一个唯一标识符,用于区分不同的享元对象。
  2. Flyweight

    • 职责:定义享元对象的接口,该接口可以接受外部状态。
    • 方法
      • operation(extrinsicState: String): void:操作方法,接受外部状态作为参数。外部状态是随上下文变化的,而内部状态是共享的。
    • 属性
      • intrinsicState: String:内部状态,是可以共享的,通常在创建时设置,并且在对象的生命周期中保持不变。
  3. ConcreteFlyweight

    • 职责:实现 Flyweight 接口,定义具体的享元对象。
    • 方法
      • operation(extrinsicState: String): void:具体的实现,使用外部状态和内部状态来完成操作。
    • 属性
      • intrinsicState: String:共享的内部状态。 

享元模式的优缺点

优点
  • 减少内存占用:通过共享对象的内部状态,减少内存使用,提高系统性能。
  • 提高创建和管理效率:享元工厂可以缓存已经创建的享元对象,减少重复创建对象的开销。
  • 模块化设计:享元模式将对象的状态分离为内部状态和外部状态,使得系统的模块化设计更加清晰。
缺点
  • 增加系统复杂性:引入享元工厂和享元对象会增加系统的复杂性,需要管理内部状态和外部状态的分离。
  • 需要外部状态的传递:每次使用享元对象时,都需要传递外部状态,这可能会增加调用的复杂性。

通过享元模式,可以有效地管理和共享对象的共有状态,从而减少内存占用,提高系统的性能。希望这些解释能帮助你更好地理解享元模式的 UML 类图及其具体实现。

享元模式在C++池化技术中的应用

享元模式在池化技术中非常有用,特别是在需要频繁创建和销毁大量相似对象的场景中。 pooling(池化技术)通过预先创建一组对象并重复使用这些对象,来减少对象创建的开销和内存的频繁分配与释放。享元模式可以进一步优化池化技术,通过共享对象的内部状态来减少内存使用。

示例:GUI资源池中的享元模式

假设我们正在开发一个GUI应用程序,需要创建大量的按钮(Button)对象。每个按钮对象都有相同的背景图片,但按钮的文本内容和位置是不同的。我们可以使用享元模式来共享按钮的背景图片,从而减少内存占用。

1. 定义享元接口
#include <iostream>
#include <string>
#include <map>
#include <memory>

class ButtonFlyweight {
public:
    virtual void draw(const std::string& text, int x, int y) const = 0;
    virtual ~ButtonFlyweight() {}
};

2. 定义具体享元
class ConcreteButtonFlyweight : public ButtonFlyweight {
private:
    std::string _backgroundImage; // 共享的内部状态
public:
    ConcreteButtonFlyweight(const std::string& backgroundImage) : _backgroundImage(backgroundImage) {}

    void draw(const std::string& text, int x, int y) const override {
        std::cout << "绘制按钮: " << text << ",位置: (" << x << ", " << y << "), 背景图片: " << _backgroundImage << std::endl;
    }
};

3. 定义享元工厂
class ButtonFlyweightFactory {
private:
    std::map<std::string, std::shared_ptr<ButtonFlyweight>> _flyweights;
public:
    std::shared_ptr<ButtonFlyweight> getButton(const std::string& backgroundImage) {
        if (_flyweights.find(backgroundImage) == _flyweights.end()) {
            std::shared_ptr<ButtonFlyweight> newButton = std::make_shared<ConcreteButtonFlyweight>(backgroundImage);
            _flyweights[backgroundImage] = newButton;
        }
        return _flyweights[backgroundImage];
    }
};

4. 定义GUI组件(使用享元)
class GUIComponent {
private:
    std::shared_ptr<ButtonFlyweight> _button;
    std::string _text;
    int _x;
    int _y;
public:
    GUIComponent(const std::string& text, int x, int y, ButtonFlyweightFactory& factory, const std::string& backgroundImage) :
        _text(text), _x(x), _y(y), _button(factory.getButton(backgroundImage)) {}

    void draw() const {
        _button->draw(_text, _x, _y);
    }
};

5. 客户端代码
int main() {
    ButtonFlyweightFactory buttonFactory;

    GUIComponent button1("Button 1", 10, 20, buttonFactory, "bg1.png");
    GUIComponent button2("Button 2", 30, 40, buttonFactory, "bg1.png");
    GUIComponent button3("Button 3", 50, 60, buttonFactory, "bg2.png");

    button1.draw();
    button2.draw();
    button3.draw();

    return 0;
}

代码解释

  1. ButtonFlyweight 接口

    • 定义了 draw 方法,该方法接受按钮的文本内容和位置(外部状态)作为参数。
  2. ConcreteButtonFlyweight 类

    • 实现了 ButtonFlyweight 接口,具体的 draw 方法使用传递进来的外部状态(按钮的文本内容和位置)和内部状态(背景图片)来绘制按钮。
  3. ButtonFlyweightFactory 类

    • 负责创建和管理 ButtonFlyweight 对象。
    • getButton 方法根据给定的背景图片返回一个享元对象。如果享元对象已经存在于缓存中,则返回缓存中的对象;否则,创建一个新的享元对象并将其加入到缓存中,然后返回。
    • _flyweights 属性是一个 map,用于存储享元对象,键是背景图片的路径。
  4. GUIComponent 类

    • 代表一个GUI组件,包含按钮的文本内容、位置等外部状态。
    • GUIComponent 构造函数接受按钮的文本内容、位置、享元工厂和背景图片路径作为参数,通过享元工厂获取享元对象。
    • draw 方法用于绘制按钮,调用享元对象的 draw 方法,传递按钮的文本内容和位置作为外部状态。
  5. 客户端代码

    • 创建 ButtonFlyweightFactory 对象。
    • 创建多个 GUIComponent 对象,每个对象使用相同的背景图片时,享元工厂会返回同一个享元对象。
    • 调用 draw 方法绘制按钮,每个按钮对象的内部状态(背景图片)是共享的,而外部状态(文本内容和位置)是在使用时传递的。

享元模式在GUI资源池中的优势

  1. 减少内存占用

    • 通过共享按钮的背景图片,可以显著减少内存使用。如果每个按钮都具有相同的背景图片,但不共享,那么每个按钮都会占用额外的内存来存储背景图片数据。
  2. 提高创建和管理效率

    • 享元工厂可以缓存已经创建的享元对象,减少重复创建对象的开销。这对于频繁创建和销毁按钮对象的场景非常有用。
  3. 模块化设计

    • 将按钮的内部状态(背景图片)和外部状态(文本内容和位置)分离,使得系统的模块化设计更加清晰。这有助于提高代码的可维护性和可扩展性。

进一步优化

在实际应用中,可以进一步优化享元模式,例如:

  • 使用智能指针:使用 std::shared_ptr 来管理享元对象的生命周期,确保对象在不再需要时被自动释放。
  • 多线程安全:如果应用是多线程的,可以在享元工厂中使用互斥锁(std::mutex)来确保线程安全。
  • 外部状态的封装:将外部状态封装在一个结构体或类中,然后传递给享元对象,以提高代码的可读性和可维护性。

通过这些优化,可以进一步提高享元模式在池化技术中的应用效果。希望这些解释和示例代码能帮助你更好地理解享元模式在C++池化技术中的应用。

 

结合 Composite 模式和 Flyweight 模式实现文档编辑器中的文本分层结构

在文档编辑器中,文本通常可以有层次结构,比如段落、行和字符等。我们可以使用 Composite 模式 来管理这种层次结构,而 Flyweight 模式 可以用来共享文本样式的内部状态,从而减少内存占用。为了进一步优化,我们还可以使用 有向无环图(DAG) 来实现 Flyweight 模式,使得多个对象可以共享复杂的内部状态。

示例代码

1. 定义享元接口
#include <iostream>
#include <string>
#include <map>
#include <memory>
#include <vector>

class TextStyle {
public:
    virtual void render(char character) const = 0;
    virtual ~TextStyle() {}
};

2. 定义具体享元
class ConcreteTextStyle : public TextStyle {
private:
    std::string _font;
    std::string _color;
    int _size;
public:
    ConcreteTextStyle(const std::string& font, const std::string& color, int size) 
        : _font(font), _color(color), _size(size) {}

    void render(char character) const override {
        std::cout << "绘制字符: " << character << ",字体: " << _font << ",颜色: " << _color << ",大小: " << _size << std::endl;
    }
};

3. 定义享元工厂
class TextStyleFactory {
private:
    std::map<std::string, std::shared_ptr<TextStyle>> _styles;
public:
    std::shared_ptr<TextStyle> getTextStyle(const std::string& font, const std::string& color, int size) {
        std::string key = font + "," + color + "," + std::to_string(size);
        if (_styles.find(key) == _styles.end()) {
            std::shared_ptr<TextStyle> newStyle = std::make_shared<ConcreteTextStyle>(font, color, size);
            _styles[key] = newStyle;
        }
        return _styles[key];
    }
};

4. 定义 Composite 模式中的组件接口
class TextComponent {
public:
    virtual void display() const = 0;
    virtual ~TextComponent() {}
};

5. 定义叶子节点(字符)
class TextCharacter : public TextComponent {
private:
    char _character;
    std::shared_ptr<TextStyle> _style;
public:
    TextCharacter(char character, const std::shared_ptr<TextStyle>& style) 
        : _character(character), _style(style) {}

    void display() const override {
        _style->render(_character);
    }
};

6. 定义组合节点(段落)
class Paragraph : public TextComponent {
private:
    std::vector<std::shared_ptr<TextComponent>> _children;
public:
    void addChild(const std::shared_ptr<TextComponent>& child) {
        _children.push_back(child);
    }

    void display() const override {
        std::cout << "段落:" << std::endl;
        for (const auto& child : _children) {
            child->display();
        }
    }
};

7. 定义组合节点(行)
class Line : public TextComponent {
private:
    std::vector<std::shared_ptr<TextComponent>> _children;
public:
    void addChild(const std::shared_ptr<TextComponent>& child) {
        _children.push_back(child);
    }

    void display() const override {
        std::cout << "行:" << std::endl;
        for (const auto& child : _children) {
            child->display();
        }
    }
};

8. 客户端代码
int main() {
    TextStyleFactory styleFactory;

    // 创建文本样式
    std::shared_ptr<TextStyle> style1 = styleFactory.getTextStyle("Arial", "Red", 12);
    std::shared_ptr<TextStyle> style2 = styleFactory.getTextStyle("Times New Roman", "Blue", 14);

    // 创建字符
    std::shared_ptr<TextComponent> charA = std::make_shared<TextCharacter>('A', style1);
    std::shared_ptr<TextComponent> charB = std::make_shared<TextCharacter>('B', style1);
    std::shared_ptr<TextComponent> charC = std::make_shared<TextCharacter>('C', style1);
    std::shared_ptr<TextComponent> charD = std::make_shared<TextCharacter>('D', style2);
    std::shared_ptr<TextComponent> charE = std::make_shared<TextCharacter>('E', style2);

    // 创建行
    std::shared_ptr<TextComponent> line1 = std::make_shared<Line>();
    line1->addChild(charA);
    line1->addChild(charB);
    line1->addChild(charC);

    std::shared_ptr<TextComponent> line2 = std::make_shared<Line>();
    line2->addChild(charD);
    line2->addChild(charE);

    // 创建段落
    std::shared_ptr<TextComponent> paragraph = std::make_shared<Paragraph>();
    paragraph->addChild(line1);
    paragraph->addChild(line2);

    // 显示文档
    paragraph->display();

    return 0;
}

代码解释

  1. TextStyle 接口

    • 定义了一个 render 方法,该方法接受一个字符作为参数,并负责绘制该字符时使用特定的文本样式。
  2. ConcreteTextStyle 类

    • 实现了 TextStyle 接口,具体的 render 方法使用传递进来的字符和内部状态(字体、颜色、大小)来绘制字符。
  3. TextStyleFactory 类

    • 负责创建和管理 TextStyle 对象。
    • getTextStyle 方法根据给定的字体、颜色和大小生成一个唯一的键,并根据该键返回一个享元对象。如果享元对象已经存在于缓存中,则返回缓存中的对象;否则,创建一个新的享元对象并将其加入到缓存中,然后返回。
    • _styles 属性是一个 map,用于存储享元对象,键是字体、颜色和大小的组合字符串。
  4. TextComponent 接口

    • 定义了一个 display 方法,该方法用于显示文本组件。
  5. TextCharacter 类

    • 代表文档中的单个字符,继承自 TextComponent 接口。
    • display 方法调用享元对象的 render 方法来绘制字符。
  6. Line 类

    • 代表文档中的一行,继承自 TextComponent 接口。
    • addChild 方法用于向行中添加字符或其他文本组件。
    • display 方法遍历并显示所有子组件。
  7. Paragraph 类

    • 代表文档中的段落,继承自 TextComponent 接口。
    • addChild 方法用于向段落中添加行或其他文本组件。
    • display 方法遍历并显示所有子组件。
  8. 客户端代码

    • 创建 TextStyleFactory 对象。
    • 创建多个 ConcreteTextStyle 对象,并通过享元工厂获取它们。
    • 创建多个字符对象 TextCharacter,并指定其样式。
    • 创建行对象 Line,并向其中添加字符。
    • 创建段落对象 Paragraph,并向其中添加行。
    • 调用 display 方法显示整个段落的层次结构。

享元模式结合 Composite 模式的优势

  1. 减少内存占用

    • 通过共享文本样式的内部状态(字体、颜色、大小),可以显著减少内存使用。特别是在文档中大量字符使用相同的样式时,效果尤为明显。
  2. 提高创建和管理效率

    • 享元工厂可以缓存已经创建的文本样式对象,减少重复创建对象的开销。这对于频繁应用相同样式的场景非常有用。
  3. 层次结构的管理

    • 使用 Composite 模式可以方便地管理文档的层次结构,如段落、行和字符。通过组合节点和叶子节点,可以灵活地构建复杂的文本结构。
  4. 模块化设计

    • 将文本样式的内部状态和外部状态(字符)分离,使得系统的模块化设计更加清晰。这有助于提高代码的可维护性和可扩展性。

进一步优化

在实际应用中,可以进一步优化享元模式和 Composite 模式,例如:

  • 使用智能指针:使用 std::shared_ptr 来管理享元对象和文本组件的生命周期,确保对象在不再需要时被自动释放。
  • 多线程安全:如果应用是多线程的,可以在享元工厂中使用互斥锁(std::mutex)来确保线程安全。
  • 外部状态的封装:将外部状态(如字符位置)封装在一个结构体或类中,然后传递给享元对象,以提高代码的可读性和可维护性。

通过这些优化,可以进一步提高享元模式在文档编辑器中文本样式中的应用效果。希望这些解释和示例代码能帮助你更好地理解如何结合 Composite 模式和 Flyweight 模式来实现文档编辑器中的文本分层结构。


http://www.kler.cn/a/456016.html

相关文章:

  • Flutter 实现文本缩放学习
  • 【最新】沃德协会管理系统源码+uniapp前端+环境教程
  • Bert各种变体——RoBERTA/ALBERT/DistillBert
  • `libaio-dev` 是什么?为什么需要安装它?
  • 其它结构型模式
  • Ribbon
  • 【vue】圆环呼吸灯闪烁效果(模拟扭蛋机出口处灯光)
  • Docker中的MYSQL导入本地SQL语句
  • 不用swipe插件,用<component>组件实现H5的swipe切换
  • 【Halcon】例程讲解:基于形状匹配与OCR的多图像处理(附图像、程序下载链接)
  • 3.若依前端项目拉取、部署、访问
  • StableSR: Exploiting Diffusion Prior for Real-World Image Super-Resolution
  • jpeg文件学习
  • SpringCloudAlibaba实战入门之路由网关Gateway断言(十二)
  • 怎么把多个PDF合并到一起-免费实用PDF编辑处理工具分享
  • Passlib库介绍及使用指南
  • 计算机组成——Cache
  • 解决gitcode 单文件上传大小10M的问题及清理缓存区
  • 探究音频丢字位置和丢字时间对pesq分数的影响
  • html+css+js网页设计 美食 美拾9个页面
  • 30天面试打卡计划 2024-12-25 26 27 面试题
  • 渗透测试常用专业术语(二)
  • 硬件开发笔记(三十二):TPS54331电源设计(五):原理图BOM表导出、元器件封装核对
  • 改进爬山算法之一:随机化爬山法(Stochastic Hill Climbing,SHC)
  • LeetCode-字符串转换整数(008)
  • 华为配置命令