zoukankan      html  css  js  c++  java
  • 设计模式(十二)—— 享元模式

    模式简介


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

    通常情况下,面向对象技术可以增强系统地灵活性及可扩展性,在系统开发过程中,我们会不断地增加类和对象。当对象数量过多时,将会带来系统开销过高、性能下降等问题。享元模式通过共享相同或相似的对象来解决这一类问题。在介绍享元模式之前,首先要弄清楚两个概念:内部状态(Intrinsic State)外部状态(Extrinsic State)

    • 内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,因此内部状态可以共享。例如,围棋中的棋子,它们的形状和大小完全相同,那么形状、大小则属于内部状态。
    • 外部状态是随环境改变而改变的、不可以共享的状态。享元对象的外部状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。一个外部状态与另一个外部状态之间是相互独立的。例如,棋子的位置各不相同,那么棋子的位置就属于外部状态。

    结构说明


    角色说明

    • Flyweight

    抽象享元类。包含一个方法,通过这个方法flyweight可以接受外部状态。

    • ConcreteFlyweight

    具体享元类。实现Flyweight,对象是可共享的。

    • UnsharedConcreteFlyweight

    非共享享元类。享元模式并不强制共享。

    • FlyweightFactory

    享元工厂类。创建并管理Flyweight对象。

    源码结构

    抽象享元类Flyweight,包含一个抽象方法Operation,并接受一个外部状态extrinsiState。

    abstract class Flyweight
    {
        public abstract void Operation(int extrinsicState);
    }
    

    具体享元类ConcreteFlyweightA和ConcreteFlyweightB,实现具体的Operation方法。

    class ConcreteFlyweightA : Flyweight
    {
        public override void Operation(int extrinsicState)
        {
            Console.WriteLine($"ConcreteFlyweightA->Operation[{extrinsicState}]");
        }
    }
    
    class ConcreteFlyweightB : Flyweight
    {
        public override void Operation(int extrinsicState)
        {
            Console.WriteLine($"ConcreteFlyweightB->Operation[{extrinsicState}]");
        }
    }
    

    享元工厂,负责维护一个享元池,用来存储具有相同内部状态的享元对象。

    class FlyweightFactory
    {
        private Dictionary<string, Flyweight> _flyweights = new Dictionary<string, Flyweight>();
        public Flyweight GetFlyweight(string key)
        {
            Flyweight flyweight;
            if (_flyweights.ContainsKey(key))
            {
                Console.WriteLine("Already in the pool , use the exist one.");
                flyweight = _flyweights[key];
            }
            else
            {
                switch (key)
                {
                    case "A":
                        flyweight = new ConcreteFlyweightA();
                        break;
                    case "B":
                        flyweight = new ConcreteFlyweightB();
                        break;
                    default:
                        throw new Exception("Don't support this key");
                }
    
                _flyweights.Add(key, flyweight);
            }
            return _flyweights[key];
        }
    }
    

    通过向工厂传入不同的key值,获取相应的享元对象。

    class Program
    {
        static void Main(string[] args)
        {
            FlyweightFactory factory = new FlyweightFactory();
            Flyweight fw = factory.GetFlyweight("A");
    
            int extrinsicState = 1;
            fw.Operation(extrinsicState);
    
            Flyweight fw2 = factory.GetFlyweight("B");
            fw2.Operation(extrinsicState);
    
            extrinsicState = 2;
            Flyweight fw3 = factory.GetFlyweight("A");
            fw3.Operation(extrinsicState);
    
            Console.ReadLine();
        }
    }
    

    输出结果:

    工作原理

    • ConcreteFlyweight对象存储内部状态,由Client计算或存储外部状态并传递给享元对象。
    • 用户通过FlyweightFactory对象获取ConcreteFlyweight对象,从而确保享元对象的共享。

    示例分析


    想象一款围棋游戏,棋盘中包含大量的黑子和白子。当然我们不能为每个棋子创建一个对象,这种设计代价太大,随着游戏的不断进行,软件占用内存会越来越大。棋子的形状、大小完全相同(内部状态),位置发生变化(外部状态)。

    首先创建享元类Chessman,包含抽象方法Display,这里以x,y表示坐标位置。

    abstract class Chessman
    {
        public abstract void Display(int x, int y);
    }
    

    创建具体享元类,为了使用方便,加入Color枚举。

    class WhiteChessman : Chessman
    {
        public override void Display(int x, int y)
        {
            Console.WriteLine($"While Chessman , Position:X=>{x},Y=>{y}");
        }
    }
    
    class BlackChessman : Chessman
    {
        public override void Display(int x, int y)
        {
            Console.WriteLine($"Black Chessman , Position:X=>{x},Y=>{y}");
        }
    }
    
    public enum Color
    {
        White,
        Black
    }
    

    创建Chessman工厂,维护一个享元池,如果享元池中包含请求的对象,则直接返回;如果不包含,则根据请求的颜色创建相应的对象,并添加至享元池中,最后返回给客户端。

    class ChessmanFactory
    {
        private Dictionary<Color, Chessman> _chessman = new Dictionary<Color, Chessman>();
        public Chessman GetChessman(Color color)
        {
            Chessman chessman;
            if (_chessman.ContainsKey(color))
            {
                Console.WriteLine("Already in the pool , use the exist one.");
                chessman = _chessman[color];
            }
            else
            {
                Console.WriteLine("new object");
                switch (color)
                {
                    case Color.White:
                        chessman = new WhiteChessman();
                        _chessman.Add(color,chessman);
                        break;
                    case Color.Black:
                        chessman = new BlackChessman();
                        _chessman.Add(color, chessman);
                        break;
                    default:
                        throw new Exception("Don't support this color");
                }
            }
    
            return chessman;
        }
    }
    

    客户端调用

    class Program
    {
        static void Main(string[] args)
        {
            ChessmanFactory factory = new ChessmanFactory();
            var chessmanA = factory.GetChessman(Color.White);
            chessmanA.Display(1,1);
    
            var chessmanB = factory.GetChessman(Color.Black);
            chessmanB.Display(4, 3);
    
            var chessmanC = factory.GetChessman(Color.White);
            chessmanC.Display(5, 10);
    
            Console.ReadLine();
        }
    }
    

    使用场景


    • 一个系统中含有大量的相似或相同的对象,由于这些对象的大量使用,消耗大量的内存。

    • 对象的大部分状态可以外部化,由客户端将这些外部状态传入对象中。

    • 由于享元模式需要维护一个存储享元对象的享元池,所以仅当在多次重复使用享元对象时才值得使用享元模式。

    优缺点


    优点

    • 极大地减少了内存中对象的数量,使得相同或相似的对象在内存中只保留一份。
    • 享元模式的外部状态相对独立,不会影响其内部状态,从而使得享元对象可以在不同环境中被共享。

    缺点

    • 系统逻辑更加复杂,需要分离出外部状态和内部状态。
    • 客户端计算并存储外部状态,导致系统运行时间变长。从某种程度上来说,享元模式是一种以时间换空间的解决方案。
  • 相关阅读:
    C. Karen and Game
    BZOJ2134: 单选错位
    BZOJ3562: [SHOI2014]神奇化合物
    BZOJ1084: [SCOI2005]最大子矩阵
    BZOJ5039: [Jsoi2014]序列维护
    BZOJ1798: [Ahoi2009]Seq 维护序列seq
    BZOJ3932: [CQOI2015]任务查询系统
    BZOJ3339: Rmq Problem
    BZOJ3585: mex
    BZOJ4196: [Noi2015]软件包管理器
  • 原文地址:https://www.cnblogs.com/Answer-Geng/p/9173513.html
Copyright © 2011-2022 走看看