用心理解设计模式——蝇量模式 / 享元模式 (Flyweight Pattern)

前置文章: 设计模式的原则 

其他设计模式:用心理解设计模式专栏

设计模式相关代码已统一放至 我的 Github

 

一、定义

  结构型模式之一。

  Use sharing to support large numbers of fine-grained objects efficiently.

 (使用共享对象可以有效地支持大量的细粒度的对象。)

二、结构解析

  蝇量模式的一般结构有三种角色:抽象蝇量、具体蝇量(可分为可共享内部状态的具体蝇量和无可共享内部状态的具体蝇量)、蝇量工厂。

  抽象蝇量(Flyweight):定义了对蝇量对象外部状态的操作接口。使用该接口,可在Client中修改蝇量对象的外部状态。

  可共享内部状态的具体蝇量(ConcreteFlyweight):维护可以共享的内部状态(因为内部状态不可变,所以一般在构造时初始化);实现操作外部状态的接口

  无可共享内部状态的具体蝇量(UnsharedConcreteFlyweight):无内部状态,所以只实现操作外部状态的接口

  蝇量工厂(FlyweightFactory):负责管理蝇量对象,形成一个对象池,提供取出对象的方法,取出时若池中对象足够,就直接返回,若对象不足则创建后返回。同时,也应提供对象放回的方法,以达到复用的目的,放回时要对外部状态进行重置,避免下次取出一个脏对象(或,不忘记在取出对象后对外部状态进行改变)。一个蝇量工厂应只管理一种蝇量对象,所以,当有多种蝇量对象时,应结合工厂模式。

三、评价

  蝇量模式,又叫享元模式(共享元素/共享内部状态)。它是一种对性能进行优化的设计模式,通过复用蝇量对象(大量类似的对象),降低系统的内存压力(复用减少对象总数)和CPU压力(复用避免了对象的实例化,有些对象的实例化是耗时操作)。

 “内部状态” 指,共享的属性, 内部状态在指定后,就无法在外部进行更改了(一般在声明或对象构造时即赋值,且不提供public的set方法)。

 “外部状态” 指,不共享的部分,外部状态将在Client中,根据环境赋予不同的值。

  举个例子: 有一种“球”,大小是固定的,颜色是不固定的,那“大小”即为球对象的内部状态,“颜色”即为球对象的外部状态。在蝇量模式中,球工厂在创建球时,即以共同大小创建球,然后交给Client,在Clinet中根据需求,对球赋予不同的颜色。

  “复用” 和 “共享” 是两个相互独立的概念。 “复用” 指,对象可以进行回收,反复使用,避免创建。 “共享” 指,对象共享内部状态。

  所以,需要注意的是,无可共享内部状态的对象,也是可以进行复用的(只复用不享元)。因此,准确来说,该模式就应该只叫做蝇量模式,而不仅仅是享元模式。

四、类和结构体的选择

  结构体是值类型,内存在栈中分配。栈的分配和释放代价比堆低的多,所以如果能使用结构体,就不用再考虑使用“类+对象池”优化。

  但使用结构体也有缺点,因为值类型是深拷贝,引用类型是浅拷贝,因此类对象在拷贝时比结构体对象更节省空间。

  需要注意的是,结构体作为值类型,应在赋值后不再改变。

  最终选用类还是结构体,还得综合考虑,如下:

------------------------------------------------------------------------------

  1、对于较大的对象应使用类,因为堆栈的空间有限。

  2、对于轻量且生命周期短的对象,可以直接使用结构体,如Unity中的 Vector2/Vector3/Vector4、Color等,就是结构体。

  3、拥有继承关系时,应使用类。

  4、如使用结构体不能避免拆装箱,就改选用类。

  5、如属性有更改需求,应使用类(结构体是值类型,应在赋值后不再改变)。

  6、不是自定义的类(如系统类)时,直接不用考虑改结构体了,只能结合对象池优化。

五、实现

  为了说明 “无可共享内部状态的具体蝇量” 也进行可以复用, 这里结合 工厂方法模式 (并且使用到了 模板方法模式),派生了 SharedFlyweightFactory 和 UnsharedFlyweightFactory 两个蝇量工厂。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Flyweight
{
    //抽象蝇量
    public abstract class Flyweight
    {
        public abstract void Operation(string externalState);
    }

    //具体蝇量, 有可共享的内部状态
    public class ConcreteFlyweight : Flyweight
    {
        private readonly string internalState;  //声明为readonly,不可改变。
        public string externalState;

        //内部状态在构造时赋值
        public ConcreteFlyweight(string internalState)
        {
            this.internalState = internalState;
        }

        //外部状态可在Client中操作
        public override void Operation(string externalState)
        {
            this.externalState = externalState;
        }
    }

    //具体蝇量, 无可共享的内部状态
    public class UnsharedConcreteFlyweight: Flyweight
    {
        public string externalState;
        public UnsharedConcreteFlyweight(){ }

        //外部状态可在Client中操作
        public override void Operation(string externalState)
        {
            this.externalState = externalState;
        }
    }

    //这里想要说明 复用和共享是两个相互独立的概念,UnsharedConcreteFlyweight 也进行可以复用。
    //因为,一个蝇量工厂应该只对应一种蝇量对象(方便扩展)。
    //所以,这里抽象出一个蝇量工厂的基类,再分别派生两个蝇量工厂类 SharedFlyweightFactory 和 UnsharedFlyweightFactory。
    public abstract class FlyweightFactory
    {
        public FlyweightFactory(int initCount)
        {
            for (int i = 0; i < initCount; i++)
            {
                CreateFlyweight();
            }
        }

        public List<Flyweight> flyweights = new List<Flyweight>();

        //从蝇量工厂中拿出蝇量对象
        public Flyweight GetFlyweight()
        {
            if (flyweights.Count > 0)
            {
                Flyweight flyweight = flyweights[0];
                flyweights.RemoveAt(0);
                return flyweight;
            }
            else
            {
                CreateFlyweight();
                return GetFlyweight();
            }
        }

        //将不使用的蝇量对象放回蝇量工厂
        public void SetFlyweight(Flyweight flyweight)
        {
            //重置外部状态
            flyweight.Operation(null);
            flyweights.Add(flyweight);
        }

        public abstract void CreateFlyweight();
    }

    //有可共享的内部状态的蝇量工厂
    public class SharedFlyweightFactory : FlyweightFactory
    {
        public SharedFlyweightFactory(int initCount): base(initCount) {}

        //创建蝇量对象
        public override void CreateFlyweight()
        {
            flyweights.Add(new ConcreteFlyweight("这个字符串代表不变、共享的“一些”内部状态"));
        }
    }

    //无可共享的内部状态的蝇量工厂
    public class UnsharedFlyweightFactory : FlyweightFactory
    {
        public UnsharedFlyweightFactory(int initCount) : base(initCount) { }

        //创建蝇量对象
        public override void CreateFlyweight()
        {
            flyweights.Add(new UnsharedConcreteFlyweight());
        }
    }

    //客户
    public class Client
    {
        static public void Main()
        {
            //创建可共享内部状态的蝇量工厂,以一定数量初始化。
            FlyweightFactory sff = new SharedFlyweightFactory(2);

            //获取蝇量对象
            Flyweight sf1 = sff.GetFlyweight();  //蝇量工厂初始化时创建的蝇量对象。
            Flyweight sf2 = sff.GetFlyweight();  //蝇量工厂初始化时创建的蝇量对象。
            Flyweight sf3 = sff.GetFlyweight();  //蝇量工厂中蝇量对象不足时,新建的蝇量对象。
            //在Client中对蝇量对象的外部状态进行操作。
            sf1.Operation("这个字符串代表变化、不共享的“一些”外部状态,123123123");
            sf2.Operation("这个字符串代表变化、不共享的“一些”外部状态,456456456");
            sf3.Operation("这个字符串代表变化、不共享的“一些”外部状态,789789789");

            //将不使用的sf3放回蝇量工厂,以便之后进行复用。
            sff.SetFlyweight(sf3);

            //获取蝇量对象
            Flyweight sf4 = sff.GetFlyweight();  //获取到了上一步放回的sf3, 达到复用的目的。

            //------------------------------------------NRatel割------------------------------------------------

            //创建不可共享内部状态的蝇量工厂,以一定数量初始化。
            FlyweightFactory usff = new UnsharedFlyweightFactory(0);

            //获取蝇量对象
            Flyweight usf1 = sff.GetFlyweight();  //蝇量工厂中蝇量对象不足时,新建的蝇量对象。
            //在Client中对蝇量对象的外部状态进行操作。
            usf1.Operation("这个字符串代表变化、不共享的“一些”外部状态,789789789");

            //将不使用的usf1放回蝇量工厂,以便之后进行复用。
            usff.SetFlyweight(usf1);

            //获取蝇量对象
            Flyweight usf2 = sff.GetFlyweight();  //获取到了上一步放回的usf1, 达到复用的目的。
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NRatel

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值