C++设计模式——享元模式(Flyweight Pattern)

C++设计模式——享元模式(Flyweight Pattern)

微信公众号:幼儿园的学霸

目录

定义

定义

Flyweight Pattern: Use sharing to support large numbers of fine-grained objects efficiently.
使用共享对象可有效地支持大量的细粒度的对象

享元(Flyweight)模式的定义:运用共享技术来有効地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。

享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。

不得不说,将Flyweight翻译为“享元”的人真是大佬,直指问题核心!

享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。

说到享元模式,第一个想到的应该就是池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应用,所以说享元模式是池技术的重要实现方式。

比如我们每次创建字符串对象时,都需要创建一个新的字符串对象的话,内存开销会很大,所以如果第一次创建了字符串对象“adam“,下次再创建相同的字符串”adam“时,只是把它的引用指向”adam“,这样就实现了”adam“字符串再内存中的共享。

需要说明一下的是,虽然可以使用享元模式实现对象池, 但是这两者还是有比较大的差异, 对象池着重在对象的复用上,池中的每个对象是可替换的,从同一个池中获得的A对象和B对象对客户端来说是完全相同的,它主要解决复用,而享元模式主要解决对象的共享问题,如何建立多个可共享的细粒度对象是其关注的重点.

举个最简单的例子,网络联机下棋的时候,一台服务器连接了多个客户端(玩家),如果我们每个棋子都要创建对象,那一盘棋可能就有上百个对象产生,玩家多点的话,因为内存空间有限,一台服务器就难以支持了,所以这里要使用享元模式,将棋子对象减少到几个实例。

在策略模式中会产生大量的策略类,是否能够通过享元模式来避免这种问题呢?

结构

享元模式以共享的方式高效地支持大量的细粒度对象,享元对象能做到共享的关键是区分内部状态(Internal State)和外部状态(External State)。其中:

  • 内部状态 是存储在享元对象内部并且不会随环境改变而改变的状态,因此内部状态可以共享。
  • 外部状态 是随环境改变而改变的、不可以共享的状态。享元对象的外部状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。一个外部状态与另一个外部状态之间是相互独立的。

模式所涉及的角色

  • 抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入;
  • 具体享元角色(Concrete Flyweight):实现抽象享元角色中所规定的接口,必须是可共享的,需要封装享元对象的内部状态;
  • 非享元角色(Unshared ConcreteFlyweight):是不可共享的外部状态,它以参数的形式注入具体享元的相关方法中;

    非共享的类根据实际情况来定,实际情况也可以没非享元角色

  • 享元工厂角色(Flyweigh tFactory):负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

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

元工厂是享元模式的核心,它需要确保系统可以共享相同的对象。一般情况下,享元工厂会维护一个对象列表,当任何组件尝试获取享元类时,如果请求的享元类已经被创建,则直接返回已有的享元类;若没有,则创建一个新的享元对象,并将它加入到维护队列中。

代码示例

代码来源于此
问题模型:围棋系统,在一个围棋系统中,有黑子、白子、还有棋盘、装棋子棋盒。其中黑子181个,白子180个,总共361个棋子,在下棋的过程中,每个棋子都会有自己的一个坐标。棋盘总共有384格,如果你为每个实例化一个棋子对象,那么将由384个棋子对象。这里我们可以使用享元模式。接下来分析内部和外部状态。对棋子、棋盘、棋盒,它们都有固定的颜色,这个是不会改变的,所以颜色是内部状态。标准双人模式下,棋盒只有两个,棋盘一个,所以它们没有共享的意义(但是它们相对于界面来说,依然有个显示坐标的问题,所以外部变量对他们依然成立)。对棋子来说,每个对象的坐标就是外部模式,根据坐标的不同,棋子可以显示在棋盘上不同的位置。

#include <bits/stdc++.h>

//抽象享元基类
class Flyweight {
public:
    virtual void display(const int iX, const int iY) = 0;

    Flyweight() = default;

    virtual ~Flyweight() = default;

protected:
    std::string m_strColor;
};


//共享类
class SharedConcreteFlyweightWhite : public Flyweight {
public:
    void display(const int iX, const int iY) override {
        std::cout << "I am a " << m_strColor
                  << " Chess,my coordinate is (" << iX << "," << iY << ")."
                  << std::endl;
    }

    SharedConcreteFlyweightWhite() {
        m_strColor = "White";
    }

    ~SharedConcreteFlyweightWhite() = default;
};


class SharedConcreteFlyweightBlack : public Flyweight {
public:
    void display(const int iX, const int iY) override {
        std::cout << "I am a black Chess,my coordinate is ("
                  << iX << "," << iY << ")" << std::endl;
    }

    SharedConcreteFlyweightBlack() : Flyweight() {
        m_strColor = "Black";
    }

    ~SharedConcreteFlyweightBlack() = default;
};


//非享元角色
class UnsharedConcreteFlyweightChessbox : public Flyweight {
public:
    void display(const int iX, const int iY) override {
        std::cout << "I am a " << m_strColor
                  << " chessbox,my coordinate is ("
                  << iX << "," << iY << ")" << std::endl;
    }

    UnsharedConcreteFlyweightChessbox() : Flyweight() {
        m_strColor = "Yellow";
    }

    ~UnsharedConcreteFlyweightChessbox() = default;
};


//享元工厂
const std::string BLACKCHESS = "Black";
const std::string WHITECHESS = "White";

class FlyweightFactory {
private:
    std::map<std::string, std::shared_ptr<Flyweight>> m_mapFlyweight;
public:
    // strKey is defined class name or surname of class
    std::shared_ptr<Flyweight> getFlyweight(const std::string strKey) {
        //if find return FlyWeight object whose key is equal,otherwise new object and insert into map
        if (m_mapFlyweight.end() != m_mapFlyweight.find(strKey))
            return m_mapFlyweight[strKey];
        if (strKey == BLACKCHESS) {
            auto pointer = std::make_shared<SharedConcreteFlyweightBlack>();
            m_mapFlyweight[strKey] = pointer;
            return pointer;
        } else if (strKey == WHITECHESS) {
            auto pointer = std::make_shared<SharedConcreteFlyweightWhite>();
            m_mapFlyweight[strKey] = pointer;
            return pointer;
        } else {
            std::cout << "The key is Error!" << std::endl;
            return nullptr;
        }
    }

    size_t getFlyweightCount() {
        return m_mapFlyweight.size();
    }
};


int main() {
    FlyweightFactory objFactory;
    auto objBlack = objFactory.getFlyweight(BLACKCHESS);
    objBlack->display(3, 5);

    auto objBlack1 = objFactory.getFlyweight(BLACKCHESS);
    objBlack1->display(1, 4);

    std::cout << "Now,total chess count " << objFactory.getFlyweightCount() << std::endl;

    auto objWhite = objFactory.getFlyweight(WHITECHESS);
    objWhite->display(9, 9);

    std::cout << "Now,total chess count " << objFactory.getFlyweightCount() << std::endl;

    auto objWhite1 = objFactory.getFlyweight(WHITECHESS);
    objWhite1->display(8, 8);

    std::cout << "Now,total chess count " << objFactory.getFlyweightCount() << std::endl;

    UnsharedConcreteFlyweightChessbox unshChessbox;
    unshChessbox.display(1, 2);

    std::cout << "Now,total chess count " << objFactory.getFlyweightCount() << std::endl;

    return 0;
    //运行结果如下:
    //I am a black Chess,my coordinate is (3,5)
    //I am a black Chess,my coordinate is (1,4)
    //Now,total chess count 1
    //I am a White Chess,my coordinate is (9,9).
    //Now,total chess count 2
    //I am a White Chess,my coordinate is (8,8).
    //Now,total chess count 2
    //I am a Yellow chessbox,my coordinate is (1,2)
    //Now,total chess count 2
}

总结

对比

  • 享元模式与单例模式的区别
    1.享元设计模式是一个类有很多对象,而单例是一个类仅一个对象。
    2.享元模式是为了节约内存空间,提升程序性能,而单例模式则主要是出于共享状态的目的。

  • 享元模式和工厂模式、单例模式
    在区分出不同种类的外部状态后,创建新对象时需要选择不同种类的共享对象,这时就可以使用工厂模式来提供共享对象,在共享对象的维护上,经常会采用单例模式来提供单实例的共享对象。

优缺点

享元模式的主要优点是:相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。

其主要缺点是:
为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
读取享元模式的外部状态会使得运行时间稍微变长。

适用场景

享元模式是通过减少内存中对象的数量来节省内存空间的,所以以下几种情形适合采用享元模式。

  • 系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源。
  • 大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
  • 由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。

参考资料

1.深入理解享元模式
2.享元模式(详解版)
3.设计模式之享元模式



下面的是我的公众号二维码图片,按需关注。
图注:幼儿园的学霸

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值