设计模式(十三)——享元模式

一、享元模式简介

1、享元模式简介

    享元模式运用共享技术有效地支持大量细粒度的对象。

    享元模式是一个考虑系统性能的设计模式,通过使用享元模式可以节约内存空间,提高系统的性能。 

    享元模式的核心在于享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存新增对象。

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

    内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,因此内部状态可以共享。

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

Flyweight模式应用中,通常修改的是外部状态属性,而内部状态属性一般都是用于参考或计算时引用。

        Flyweight执行时所需的状态必定是内部的或外部的。内部状态存储于ConcreteFlyweight对象之中;而外部状态则由Client对象存储或计算。当用户调用Flyweight对象的操作时,将外部状态传递给享元对象

2、享元模式角色

        Flyweight享元抽象类定义所有享元的基类,定义一个接口,通过定义的接口Flyweight可以接受并作用于外部状态。

        ConcreteFlyweight具体享元类继承Flyweight抽象类并实现其定义的接口,并为内部状态(如果有的话)增加存储空间。ConcreteFlyweight对象必须是可共享的所存储的状态必须是内部的(intrinsic即必须独立于ConcreteFlyweight对象的场景。

        UnsharedConcreteFlyweight非共享具体享元类:并非所有的Flyweight子类都需要被共享。Flyweight接口使共享成为可能,但并不强制共享。在Flyweight对象结构的某些层次,UnsharedConcreteFlyweight对象通常将ConcreteFlyweight对象作为子节点。

        FlyweightFactory享元工厂:用来创建并管理Flyweight对象,主要用来确保合理地共享Flyweight。当用户请求一个Flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话)FlyweightFactory必须保证享元对象可以被系统适当地共享。当一个客户端对象调用一个享元对象flyweight的时候,享元工厂角色(Flyweight Factory对象)会检查系统中是否已经有一个符合要求的享元对象。如果已经有,享元工厂角色就应当提供已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个合适的享元对象。
    客户(Client:维持一个对flyweight的引用。计算或存储一个(多个)flyweight的外部状态。用户不应直接对ConcreteFlyweight类进行实例化,而只能从FlyweightFactory对象得到ConcreteFlyweight对象,保证对享元对象适当地进行共享

    享元模式可以避免大量非常相似类的开销。在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现实例数据除了几个参数外基本都是相同的。有时就能够大幅度地减少实例化的类的数量。如果能把不相同的参数移到类实例的外面,在方法调用时将它们传递进来,就可以通过共享大幅度地减少单个实例的数目。

3、享元模式优缺点

享元模式的优点:

A、享元模式的优点在于可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份。

B、享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。

享元模式的缺点:

A、享元模式使得系统更加复杂,需要分离出内部状态和外部状态,使得程序的逻辑复杂化。

B、为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。

4、享元模式使用场景

    享元模式使用场景:

    A一个应用程序使用大量相同或者相似的对象,造成很大的存储开销。

    B对象的大部分状态都可以外部化,可以将外部状态传入对象中。

    C如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。

    D应用程序不依赖于对象标识。由于Flyweight对象可以被共享,对于概念上明显有别的对象,标识测试将返回真值。

    E使用享元模式需要维护一个存储享元对象的享元池,而这需要耗费资源,因此,应当在多次重复使用享元对象时才值得使用享元模式

    F适合用于当大量物件只是重复因而导致无法令人接受的使用大量内存。通常物件中的部分状态是可以分享。常见做法是把它们放在外部数据结构,当需要使用时再将它们传递给享元。

    客户端要引用享元对象,是通过工厂对象创建或者获得的,客户端每次引用一个享元对象,都是可以通过同一个工厂对象来引用所需要的享元对象。因此,可以将享元工厂设计成单例模式,保证客户端只引用一个工厂实例。因为所有的享元对象都是由一个工厂对象统一管理的,所以在客户端没有必要引用多个工厂对象。不管是单纯享元模式还是复合享元模式中的享元工厂角色,都可以设计成为单例模式,对于结果是不会有任何影响的。

     组合模式:享元模式通常和组合模式结合起来,用共享叶结点的有向无环图实现一个逻辑上的层次结构。组合享元模式实际上是单纯享元模式与组合模式的组合。单纯享元对象可以作为树叶对象来讲,是可以共享的,而组合享元对象可以作为树枝对象, 因此在复合享元角色中可以添加聚集管理方法。通常,最好用Flyweight实现StateStrategy对象。

二、享元模式实现

Flyweight享元抽象类:

#ifndef FLYWEIGHT_H#define FLYWEIGHT_H#include 
#include 
using namespace std; //享元抽象类class Flyweight{public:    virtual ~Flyweight(){}    string getIntrinsicState()    {        return m_intrinsicState;    }    //定义操作外部状态的接口    virtual void operation(const string& extrinsicState) = 0;protected:    Flyweight(string intrinsicState)    {        m_intrinsicState = intrinsicState;    }private:    //内部状态,放在ConcreteFlyweight内    string m_intrinsicState;}; #endif // FLYWEIGHT_H

ConcreteFlyweight具体享元类:

#ifndef CONCRETEFLYWEIGHT_H#define CONCRETEFLYWEIGHT_H#include "Flyweight.h" //具体享元对象类class ConcreteFlyweight : public Flyweight{public:    ConcreteFlyweight(string intrinsicState):Flyweight(intrinsicState){}    ~ConcreteFlyweight(){}    //实现操作外部状态的接口    void operation(const string& extrinsicState)    {        cout << "IntrinsicState: " << getIntrinsicState() << endl;        cout << "ExtrinsicState: " << extrinsicState << endl;    }}; #endif // CONCRETEFLYWEIGHT_H

UnsharedConcreteFlyweight非享元具体类:

#ifndef UNSHAREDCONCRETEFLYWEIGHT_H#define UNSHAREDCONCRETEFLYWEIGHT_H#include "Flyweight.h" class UnsharedConcreteFlyweight : public Flyweight{public:    UnsharedConcreteFlyweight(string intrinsicState):Flyweight(intrinsicState){}    ~UnsharedConcreteFlyweight(){}    void operation(const string& extrinsicState)    {        cout << "ExtrinsicState: " << extrinsicState << endl;    }}; #endif // UNSHAREDCONCRETEFLYWEIGHT_H

FlyweightFactory享元工厂类:

#ifndef FLYWEIGHTFACTORY_H#define FLYWEIGHTFACTORY_H#include "Flyweight.h"#include "ConcreteFlyweight.h"#include 
 class FlyweightFactory{public:    FlyweightFactory(){}    ~FlyweightFactory()    {        m_flyweights.clear();    }    //获得一个请求的Flyweight对象,若该对象已存在,直接返回,否则新建一个对象,存入容器中,再返回    Flyweight* getFlyweight(string key)    {        vector
::iterator iter = m_flyweights.begin();        for(; iter != m_flyweights.end(); iter++)        {            if((*iter)->getIntrinsicState() == key)            {                return *iter;            }        }        Flyweight* fly = new ConcreteFlyweight(key);        m_flyweights.push_back(fly);        return fly;    }    //获取容器中存储的对象数量    void getFlyweightCount()    {        cout << m_flyweights.size() << endl;    } private:    //保存内部状态对象的容器    vector
 m_flyweights;}; #endif // FLYWEIGHTFACTORY_H

客户调用程序:

#include "FlyweightFactory.h"#include "Flyweight.h"#include "UnsharedConcreteFlyweight.h" int main(){    //外部状态    string extrinsicState = "ext";    //工厂对象    FlyweightFactory* factory = new FlyweightFactory();    //向工厂申请三个Flyweight对象,且对象的内部状态值为“A”    //对象池中实际只有一个Flyweight对象,实现共享    Flyweight* flyweightA = factory->getFlyweight("A");    Flyweight* flyweightA1 = factory->getFlyweight("A");    Flyweight* flyweightA2 = factory->getFlyweight("A");    //向工厂申请一个Flyweight对象,且对象的内部状态值为“B”    Flyweight* flyweightB = factory->getFlyweight("B");    Flyweight* flyweightC = new UnsharedConcreteFlyweight("C");    //应用外部状态    flyweightA->operation(extrinsicState);    flyweightA1->operation(extrinsicState);    flyweightA2->operation(extrinsicState);    flyweightB->operation(extrinsicState);    flyweightC->operation(extrinsicState);    //对象池中有两个享元对象    factory->getFlyweightCount();     delete factory;     return 0;}

三、享元模式实例

围棋实例:

    在围棋中,棋子就是大量细粒度的对象。其属性有内在的,比如颜色、形状等,也有外在的,比如在棋盘上的位置。内在的属性是可以共享的,区分在于外在属性。因此,只需定义两个棋子的对象,一颗黑棋和一颗白棋,黑棋和白棋包含棋子的内在属性;棋子的外在属性,即在棋盘上的位置可以提取出来,存放在单独的容器中。整个系统中只有一颗黑棋和一颗白棋,大大减少了对空间的需求。

Piece享元抽象类:

#ifndef PIECE_H#define PIECE_H#include 
using namespace std; //棋子颜色enum PieceColor{BLACK, WHITE};//棋子位置struct PiecePos{    int x;    int y;    PiecePos(int a, int b): x(a), y(b) {}};//享元抽象类class Piece{protected:    PieceColor m_color; //颜色public:    //执子操作接口    virtual void move() = 0;protected:    //不能直接使用Piece    Piece(PieceColor color): m_color(color) {}}; #endif // PIECE_H

BlackPiece具体享元类:

#ifndef BLACKPIECE_H#define BLACKPIECE_H#include "Piece.h" //具体享元对象实现类class BlackPiece : public Piece{public:    BlackPiece(PieceColor color): Piece(color) {}    void move()    {        cout << "move an BlackPiece";    }}; #endif // BLACKPIECE_H

WhitePiece具体享元类:

#ifndef WHITEPIECE_H#define WHITEPIECE_H#include "Piece.h" //具体享元对象实现类class WhitePiece : public Piece{public:    WhitePiece(PieceColor color): Piece(color) {}    void move()    {        cout << "move an WhitePiece";    }}; #endif // WHITEPIECE_H

PieceBoard享元工厂类:

#ifndef PIECEBOARD_H#define PIECEBOARD_H#include "Piece.h"#include 
#include "BlackPiece.h"#include "WhitePiece.h" //享元工厂类class PieceBoard{    public:    PieceBoard(string black, string white): m_blackName(black), m_whiteName(white)    {        m_blackPiece = NULL;        m_whitePiece = NULL;    }    ~PieceBoard()    {        delete m_blackPiece;        delete m_whitePiece;    }    void setPiece(PieceColor color, PiecePos pos)    {        if(color == BLACK)        {            if(m_blackPiece == NULL)  //只有一颗黑棋                m_blackPiece = new BlackPiece(color);            cout <
<< " ";            m_blackPiece->move();            cout <<" on pos("<
<<','<
<<")" << endl;        }        else        {            if(m_whitePiece == NULL)//只有一颗白棋                m_whitePiece = new WhitePiece(color);            cout <
<< " ";            m_whitePiece->move();            cout <<" on pos("<
<<','<
<<")" << endl;        }        m_vecPos.push_back(pos);    }private:    //内部属性,共享    Piece *m_blackPiece;//黑棋棋子    Piece *m_whitePiece;//白棋棋子    //外部属性    string m_blackName;//黑手    string m_whiteName;//白手    vector
 m_vecPos; //存放棋子的位置}; #endif // PIECEBOARD_H

 

客户调用程序:

#include "PieceBoard.h" int main(){    PieceBoard pieceBoard("A", "B");    pieceBoard.setPiece(WHITE, PiecePos(4, 4));    pieceBoard.setPiece(BLACK, PiecePos(4, 16));    return 0;}