• 设计模式学习笔记(二十:享元模式)


    1.1概述

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

        一个类中的成员变量表明该类所创建对象所具有的属性,在某些程序设计中可能用一个类创建若干个对象,但是发现这些对象的一个共同特点是它们有一部分属性的取值必须是完全相同的。

      例如,一个Car类,其类图如下图一所示:

     

    图一:Car

      当用Car类创建若干个的同型号的轿车时,比如创建若干个“奥迪A6”轿车,要求这些轿车的heightwidthlength值都必须是相同的(轿车的属性很多,属于细粒度对象,而且轿车的很多属性值是相同的,这里只示意了heightwidthlength三个属性),而colorpower可以是不同的,就像很多“奥迪A6”轿车,他们的长度、高度和宽度都是相同的,但颜色和动力可能不同。

      从创建对象的角度来看,我们面对的问题是:Car的每个对象的变量都各自占有不同的内存空间。这样一来,Car创建的对象越多就越浪费内存,而且程序也无法保证Car类创建的多个对象所对应的heightwidthlength值是相同的或者禁止Car类对象随意修改自己的heightwidthlengh值。

      现在重新设计Car类,由于要求Car类所创建的若干个对象的heightwidthlength值都必须是相同的,因此没有必要为每个Car对象的heightwidthlength分配不同的内存空间,现在将Car类中的heightwidthlength封装到另一个CarData类中,CarData类的类图如下图二所示:

     

    图二:CarData

      现在,假设系统能保证向Car类的若干个对象提供相同的CarData实例,即可以让Car类的若干个对象共享CarData类的一个实例,那么就可以对Car类进行修改,修改后的Car类包含CarData实例,修改后的Car类类图如下图三所示:

     

    图三:修改后的Car

      这样一来,Car类创建的若干个对象的colorpower都分配不同的内存空间,但是这些对象共享一个由系统提供的carData对象,节省了内存开销。

     

    1.2模式的结构

    享元模式包括以下三种角色:

    (1)享元接口(Flyweight):是一个接口,该接口定义了享元对外公开其内部数据的方法,以及享元接收外部数据的方法。

    (2)具体享元(Concrete Flyweight):实现享元接口的类,该类的实例称为享元对象,或简称享元。具体享元类的成员变量为享元对象的内部状态,享元对象的内部状态必须与所处的周围环境无关,即要保证使用享元对象的应用程序无法更改享元的内部状态,只有这样才能使享元对象在系统中被共享。因为享元对象是用来共享的,所以不能允许用户各自地使用具体享元类来创建对象,这样就无法达到共享的目的,因为不同用户用具体享元类创建的对象显然是不同的,所以,具体享元类的构造方法必须是private的,其目的是不允许用户程序直接使用具体享元类来创建享元对象,创建和管理享元对象有享元工厂负责。

    (3)享元工厂(Flyweight Factory):享元工厂是一个类,该类的实例负责创建和管理享元对象,用户或其他对象必须请求享元工厂为它得到一个享元对象。享元工厂可以通过一个散列表(也称作共享池)来管理享元对象,当用户程序或其他若干个对象向享元工厂请求一个享元对象时,如果享元工厂的散列表中已有这样的享元对象,享元工厂就提供这个享元对象给请求者,否则就创建一个享元对象添加到散列表中,同时将该享元对象提供给请求者。显然,当若干个用于或对象请求享元工厂提供一个享元对象时,第一个用户获得该享元对象的时间可能慢一些,但是后继的用户会较快地获得这个享元对象。可以使用单例模式来设计这个享元工厂,即让系统中只有一个享元工厂的实例,另外,为了让享元工厂能生成享元对象,需要将具体享元类作为享元工厂的内部类。

    享元模式结构的类图如下图四所示:

     

    图四:享元模式的类图

     

    1.3享元模式的优点

    (1)使用享元可以节省内存的开销,特别适合处理大量细粒度对象,这些对象的许多属性值是相同的,而且一旦创建则不容许修改。

    (2)享元模式中的享元可以使用方法的参数接受外部状态中的数据,但外部状态数据不会干扰到享元中的内部数据,这就使享元可以在不同的环境中被共享。

     

     

    1.4适合使用享元模式的情景

    1)一个应用程序使用大量的对象,这些对象之间部分属性本质上是相同的,这时应使用享元来封装相同的部分。

    2)对象的多数状态都可改变为外部状态,就可以考虑将这样对象作为系统中的享元来使用。

    1.5享元模式的使用

    以下通过一个简单的问题来描述怎样使用享元模式,这个简单的问题是:创建若干个“奥迪A6”轿车和若干个“奥迪A4”轿车。“奥迪A6”轿车长、宽、高都是相同的,颜色和功率可以不同;“奥迪A4”轿车的长、宽和高都是相同的,颜色和功率可以不同。

     首先看一下本实例构建框架具体类和1.2模式的结构中类图的对应关系,如下图所示:

    (1)享元接口(Flyweight

    本问题中,应当使用享元对象封装轿车的长、宽和高数据,颜色和功率作为享元对象的外部数据,这些外部数据可以传递给享元对象的方法的参数,因此,给出的享元接口Flyweight不仅包含有返回内部数据的方法,也包含有获得外部数据的方法,具体代码如下:

    package com.liuzhen.twenty_flyweight;
    
    public interface Flyweight {
        public double getHeight();   //返回内部数据
        public double getWidth();
        public double getLength();
        public void printMess(String mess);    //使用参数mess获取外部数据
    }

    (2)享元工厂(FlyweightFactory)与具体享元

    享元工厂是FlyweightFactory类,负责创建和管理享元对象。FlyweightFactory将创建享元对象的具体享元类ConcreteFlyweight类作为自己的内部类,代码如下:

    package com.liuzhen.twenty_flyweight;
    
    import java.util.HashMap;
    
    public class FlyweightFactory {
        private HashMap<String,Flyweight> hashMap;
        static FlyweightFactory factory = new FlyweightFactory();
        
        private FlyweightFactory(){
            hashMap = new HashMap<String,Flyweight>();
        }
        
        public static FlyweightFactory getFactory(){
            return factory;
        }
        
        public synchronized Flyweight getFlyweight(String key){
            if(hashMap.containsKey(key))
                return  hashMap.get(key);
            else{
                double width = 0,height = 0,length = 0;
                String[] str = key.split("#");
                width = Double.parseDouble(str[0]);
                height = Double.parseDouble(str[1]);
                length = Double.parseDouble(str[2]);
                Flyweight ft = new ConcreteFlyweight(width,height,length);
                hashMap.put(key, ft);
                return ft;
            }
        }
        
        class ConcreteFlyweight implements Flyweight{  //ConcreteFlyweight是内部类
            private double width;
            private double height;
            private double length;
            
            private ConcreteFlyweight(double width,double height,double length){
                this.width = width;
                this.height = height;
                this.length = length;
            }
            
        
            public double getHeight(){
                return height;
            }
            
            public double getLength(){
                return length;
            }
    
            public double getWidth() {
                return width;
            }
    
            public void printMess(String mess) {
                System.out.print(mess);     //输出外部数据mess
                System.out.print(" 宽度:"+width);  //输出内部数据width
                System.out.print(" 高度:"+height);
                System.out.println(" 长度:"+length);
                
            }
            
        }
    }

    3)具体使用

      通过TwentyApplication类来具体实现上述相关类和接口,来实现享元模式的运用,其代码如下:

    package com.liuzhen.twenty_flyweight;
    
    public class TwentyApplication {
        
        public static void main(String[] args){
            FlyweightFactory factory = FlyweightFactory.getFactory();
            double width = 1.82,height = 1.47,length = 5.12;
            String key = ""+width+"#"+height+"#"+length;
            Flyweight flyweight = factory.getFlyweight(key);
            Car aodiA6One = new Car(flyweight,"奥迪A6","黑色",128);
            Car aodiA6Two = new Car(flyweight,"奥迪A6","灰色",160);
            //aodiA6One和aodiA6Two没有向享元传递外部数据,而是获取享元的内部数据
            aodiA6One.print();
            aodiA6Two.print();
            width = 1.77;
            height = 1.45;
            length = 4.63;
            key = ""+width+"#"+height+"#"+length;
            flyweight = factory.getFlyweight(key);
            Car aodiA4One = new Car(flyweight,"奥迪A4","蓝色",126);
            Car aodiA4Two = new Car(flyweight,"奥迪A4","红色",138);
            //aodiA4One和aodiA4Two向享元传递外部数据,这些数据是不共享的
            flyweight.printMess(" 名称:奥迪A4 颜色:蓝色 功率:126");
            flyweight.printMess(" 名称:奥迪A4 颜色:红色 功率:138");
        }
    }

    其中使用的Car类具体代码:

    package com.liuzhen.twenty_flyweight;
    
    public class Car {
        Flyweight flyweight;
        String name,color;
        int power;
        
        public Car(Flyweight flyweight,String name,String color,int power){
            this.flyweight = flyweight;
            this.name = name;
            this.color = color;
            this.power = power;
        }
        
        
    
        public void print(){
            System.out.print(" 名称:"+name);
            System.out.print(" 颜色:"+color);
            System.out.print(" 功率:"+power);
            System.out.print(" 宽度:"+flyweight.getWidth());
            System.out.print(" 高度:"+flyweight.getHeight());
            System.out.println(" 长度:"+flyweight.getLength());
        }
    }

    运行结果:

     名称:奥迪A6 颜色:黑色 功率:128 宽度:1.82 高度:1.47 长度:5.12
     名称:奥迪A6 颜色:灰色 功率:160 宽度:1.82 高度:1.47 长度:5.12
     名称:奥迪A4 颜色:蓝色 功率:126 宽度:1.77 高度:1.45 长度:4.63
     名称:奥迪A4 颜色:红色 功率:138 宽度:1.77 高度:1.45 长度:4.63

    参考资料:

          1.Java设计模式/耿祥义,张跃平著.——北京:清华大学出版社,2009.5

  • 相关阅读:
    转“C++之文件IO操作流”
    编译过程的一些小知识——#pragma once与 #ifndef的区别以及介绍
    编译过程的一些小知识——内部连接与外部连接
    VC MFC如何使用Console输出调试信息..
    windows 下ping命令,t选项
    电力间隔定义
    这样的要求不过分
    拾零之 :if 判断顺序的问题
    CMarkup 解析XML
    XinZhou housing mobile phone
  • 原文地址:https://www.cnblogs.com/liuzhen1995/p/6042919.html
Copyright © 2020-2023  润新知