zoukankan      html  css  js  c++  java
  • 设计模式之享元模式

    享元模式--导读

      我们都应该看过围棋类的游戏吧,如果看过的话我们就会发现,在围棋中有黑白两种棋子,而且每个棋子在棋盘上的位置不同,如果我要对棋盘上的每个棋子进行存储的话。那么这样势必会浪费很多内存,计算机内存空间非常有限,如果仅仅为模拟棋子就花费这么多内存的话,那么势必会导致这个游戏的淘汰。于是我们便想着如何对存储模式进行优化。这时我们会发现每一种围棋只是位置不同,而其他都相同,比如说围棋的大小颜色都相同。于是我们便想到了享元模式来解决该问题。

    享元模式--概述

          当一个软件系统在运行时产生的对象数量太多,将导致运行代价过高,带来系统性能下降等问题。例如在一个文本字符串中存在很多重复的字符,如果每一个字符都用一个单独的对象来表示,将会占用较多的内存空间,那么我们如何去避免系统中出现大量相同或相似的对象,同时又不影响客户端程序通过面向对象的方式对这些对象进行操作?享元模式正为解决这一类问题而诞生。享元模式通过共享技术实现相同或相似对象的重用,在逻辑上每一个出现的字符都有一个对象与之对应,然而在物理上它们却共享同一个享元对象,这个对象可以出现在一个字符串的不同地方,相同的字符对象都指向同一个实例,在享元模式中,存储这些共享实例对象的地方称为享元池(Flyweight Pool)。我们可以针对每一个不同的字符创建一个享元对象,将其放在享元池中,需要时再从享元池取出。

        享元模式以共享的方式高效地支持大量细粒度对象的重用,享元对象能做到共享的关键是区分了内部状态(Intrinsic State)外部状态(Extrinsic State)。下面将对享元的内部状态和外部状态进行简单的介绍:

          (1)  内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享。如字符的内容,不会随外部环境的变化而变化,无论在任何环境下字符“a”始终是“a”,都不会变成“b”。

          (2)  外部状态是随环境改变而改变的、不可以共享的状态。享元对象的外部状态通常由客户端保存,并在享元对象被创建之后,需要使用的时候再传入到享元对象内部。一个外部状态与另一个外部状态之间是相互独立的。如字符的颜色,可以在不同的地方有不同的颜色,例如有的“a”是红色的,有的“a”是绿色的,字符的大小也是如此,有的“a”是五号字,有的“a”是四号字。而且字符的颜色和大小是两个独立的外部状态,它们可以独立变化,相互之间没有影响,客户端可以在使用时将外部状态注入享元对象中。

          正因为区分了内部状态和外部状态,我们可以将具有相同内部状态的对象存储在享元池中,享元池中的对象是可以实现共享的,需要的时候就将对象从享元池中取出,实现对象的复用。通过向取出的对象注入不同的外部状态,可以得到一系列相似的对象,而这些对象在内存中实际上只存储一份。

    享元模式--结构

      下图是享元模式的UML结构图

    2222

          享元模式存在如下几个角色:

          Flyweight: 抽象享元类。所有具体享元类的超类或者接口,通过这个接口,Flyweight可以接受并作用于外部专题 
          ConcreteFlyweight: 具体享元类。指定内部状态,为内部状态增加存储空间。 
          UnsharedConcreteFlyweight: 非共享具体享元类。指出那些不需要共享的Flyweight子类。 
          FlyweightFactory: 享元工厂类。用来创建并管理Flyweight对象,它主要用来确保合理地共享Flyweight,当用户请求一个Flyweight时,FlyweightFactory就会提供一个已经创建的Flyweight对象或者新建一个(如果不存在)。

        在享元模式中引入了享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,当用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。典型的享元工厂类的代码如下:

    class FlyweightFactory {
    
        //定义一个HashMap用于存储享元对象,实现享元池
    
           private HashMap flyweights = newHashMap();
    
          
    
           public Flyweight getFlyweight(String key){
    
                  //如果对象存在,则直接从享元池获取
    
                  if(flyweights.containsKey(key)){
    
                         return(Flyweight)flyweights.get(key);
    
                  }
    
                  //如果对象不存在,先创建一个新的对象添加到享元池中,然后返回
    
                  else {
    
                         Flyweight fw = newConcreteFlyweight();
    
                         flyweights.put(key,fw);
    
                         return fw;
    
                  }
    
           }
    
    }

    享元模式--代码实现

      下面我以对棋盘上棋子的保存来进行代码实现:

    ChessFlyweight.java用于抽象定义一个棋子类
    package Flyweight_Pattern;
    /**
     * 定义一个抽象的享元类
     * 在实例中表示为棋子的父类
     * @author xyxy001
     *
     */
    public abstract class ChessFlyweight {
        
        //内部状态,由创建时进行设置,也可以在构造器中定义
        public abstract String  getColor();
        
        //外部状态展示,由外部自定义注入
        public void display(ChessLocation cl) {
            System.out.println("棋子的颜色"+this.getColor());
            System.out.println("棋子的位置"+cl.getX()+","+cl.getY());
        }
    }
    FlyweightFactory.java用于创建享元对象
    package Flyweight_Pattern;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * 享元工厂,用于创建并管理享元对象
     * @author xyxy001
     *
     */
    public class FlyweightFactory {
        //定义一个存储享元对象集合,用于模拟享元池
        private   Map<String,ChessFlyweight> flyWeightPool;
        
        private static FlyweightFactory instance;
        
        //私有化构造方法利用单例模式实现
        private FlyweightFactory(){
            flyWeightPool=new HashMap<String,ChessFlyweight>();
            
        }
        
        public  ChessFlyweight getChess(String type){
            ChessFlyweight abstractchess=flyWeightPool.get(type);
            if(abstractchess==null){
                if(type.equals("白色"))
                {
                    WhiteChess white=new WhiteChess();
                    flyWeightPool.put("黑色", white);
                    return white;
                }else{
                    BlackChess black=new BlackChess();
                    flyWeightPool.put("黑色", black);
                    return black;
                }
            }else
                return abstractchess;
        }
        
        public static FlyweightFactory getInstance(){
            return new FlyweightFactory();
        }
    }
    BlackChess具体的享元对象
    package Flyweight_Pattern;
    /**
     * 模拟黑色的棋子
     * @author xyxy001
     *
     */
    public class BlackChess extends ChessFlyweight {
        
        
        @Override
        public String getColor() {
            
            return "黑色";
        }
    
    }
    WhiteChess另一个具体的享元对象
    package Flyweight_Pattern;
    /**
     * 模拟白色的棋子,其中内部状态直接在创建的时候赋予
     * @author xyxy001
     *
     */
    public class WhiteChess extends ChessFlyweight {
        
        @Override
        public String getColor() {
            // TODO Auto-generated method stub
            return "白色";
        }
    
        
    
    }
    ChessLocation棋子的一个外部状态
    package Flyweight_Pattern;
    /**
     * 定义一个围棋位置的类,用于进行外部注入
     * @author xyxy001
     *
     */
    public class ChessLocation {
        private int x,y;
        public ChessLocation(int x,int y){
            this.x=x;
            this.y=y;
        }
        public int getX() {
            return x;
        }
        public void setX(int x) {
            this.x = x;
        }
        public int getY() {
            return y;
        }
        public void setY(int y) {
            this.y = y;
        }
        
    }

    client用于模拟客户端

    package Flyweight_Pattern;
    
    public class client {
        public static void main(String[] args) {
            //获取一个享元工厂
            FlyweightFactory factory=FlyweightFactory.getInstance();
            
            //获取两个黑色棋子,和一个白色棋子
            BlackChess b1,b2;
            WhiteChess c1;
            b1=(BlackChess)factory.getChess("黑色");
            b2=(BlackChess)factory.getChess("黑色");
            c1=(WhiteChess)factory.getChess("白色");
            
            //判断b1,b2是否是同一个对象
            System.out.println(b1==b2);
            
            System.out.println("/***************注入外部状态*************/");
            System.out.println("****************第一个黑色的棋子************");
            b1.display(new ChessLocation(4,5));
            System.out.println("****************第二个黑色的棋子*************");
            b2.display(new ChessLocation(56,187));
            System.out.println("****************第三个白色的棋子**************");
            c1.display(new ChessLocation(89,12));
            
        }
    }

    运行效果

    享元模式--适用场景

        1、如果一个系统中存在大量的相同或者相似的对象,由于这类对象的大量使用,会造成系统内存的耗费,可以使用享元模式来减少系统中对象的数量。

          2、对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。

    享元模式--优缺点

    优点

          1、享元模式的优点在于它能够极大的减少系统中对象的个数。

          2、享元模式由于使用了外部状态,外部状态相对独立,不会影响到内部状态,所以享元模式使得享元对象能够在不同的环境被共享。

    缺点

          1、由于享元模式需要区分外部状态和内部状态,使得应用程序在某种程度上来说更加复杂化了。

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

  • 相关阅读:
    奇迹银桥「1」
    20190729-“退役”专场
    20190727-只是睡着了
    $mathcal{Miemeng}$的病态码风计划
    20190725-Silly
    作业-[luogu4396][AHOI2013]-莫队
    数学网学笔记
    20190722-Moni和Boly的故事
    数学学习笔记
    20190719-FirstZero
  • 原文地址:https://www.cnblogs.com/sank/p/10713021.html
Copyright © 2011-2022 走看看