设计模式讲解与代码实践(十二)——享元

本文来自李明子csdn博客(http://blog.csdn.net/free1985),商业转载请联系博主获得授权,非商业转载请注明出处!

1 目的

享元(Flyweight)设计模式用于大量细粒度对象的共享使用。

2 基本形态

享元的基本形态如类图2-1所示。
设计模式讲解与代码实践(十二)——享元_第1张图片
图2-1 享元类图

享元被多个客户共享使用,对象间的交互如对象图2-2所示。
设计模式讲解与代码实践(十二)——享元_第2张图片
图2-2 享元对象图

3 参与者

结合图2-1,下面介绍各类在享元设计模式中扮演的角色。
3.1 Flyweight
Flyweight是享元接口。它声明了享元提供的各接口方法。
3.2 ConcreteFlyweight
ConcreteFlyweight是具体享元类,实现了享元Flyweight接口。ConcreteFlyweight的状态分为内、外部两部分。内部状态由ConcreteFlyweight维护,在客户间共享;外部状态不在其维护范围,由客户调用其接口方法时传入。ConcreteFlyweight对象是真正在客户间共享的对象。
3.3 UnsharedConcreteFlyweight
UnsharedConcreteFlyweight是非共享的具体享元类,实现了享元Flyweight接口。并不是所有实现了享元接口的类对象都必须要共享,UnsharedConcreteFlyweight类对象就不会被共享。另外,与ConcreteFlyweight相比,UnsharedConcreteFlyweight还维护了全部状态。
3.4 FlyweightFactory
FlyweightFactory是享元工厂,用于制造享元对象。FlyweightFactory内部维护了一个享元池,当要获取的享元对象已存在时从享元池中返回该对象,否则创建该对象返回,并将其加入享元池。
3.5 Client
Client是客户,是享元模式的使用者。它通过享元工厂生产享元对象,维护享元对象的外部状态,并通过传入这些外部状态调用享元的具体方法。

4 代码实践

下面我们用一个业务场景实例来进一步讲解享元的使用。
4.1 场景介绍
某地图应用绘制的地图包含各种图标,这些图标中既有指定类型的图标又有自定义图标。其中,指定类型图标根据类型显示相应的图标,自定义类型图标根据指定图标文件url显示图标。
以下各节将介绍该场景各类的具体实现及其在享元设计模式中所对应的参与者角色。
4.2 IIconDrawer
IIconDrawer是图标绘制器接口。它定义了绘制图标接口方法draw。对应于享元模式的参与者,IIconDrawer是享元Flyweight。下面的代码给出了IIconDrawer的声明。

package demo.designpattern.flyweight;

/**
 * 地图标号
 * Created by LiMingzi on 2017/7/24.
 */
public interface IIconDrawer {
    /**
     * 绘制图标
     * @param x 横坐标
     * @param y 纵坐标
     * @param name 显示名称
     */
    void draw(int x,int y,String name);
}

上述代码中,14行,接口draw需要外部传入横、纵坐标及显示名用于绘制图标。
4.3 IconDrawer
IconDrawer是具体图标绘制器类,实现了IIconDrawer接口,用于绘制图标。对应于享元模式的参与者,IconDrawer是具体享元类ConcreteFlyweight。下面的代码给出了IconDrawer的声明。

package demo.designpattern.flyweight;

/**
 * 图标绘制器
 * Created by LiMingzi on 2017/7/24.
 */
public class IconDrawer implements IIconDrawer {
    /**
     * 图标类型
     */
    private String type;

    /**
     * 构造方法
     * @param type 图标类型
     */
    public IconDrawer(String type) {
        this.type = type;
    }

    /**
     * 绘制图标
     *
     * @param x    横坐标
     * @param y    纵坐标
     * @param name 显示名称
     */
    @Override
    public void draw(int x, int y, String name) {
        System.out.println("在坐标("+x+","+y+")绘制了名为\""+name+"\"的"+type+"类型图标");
    }
}

上面的代码中,11行,IconDrawer 类维护了图标类型type。该成员变量是在类实例化时通过构造方法初始化的。
4.4 CustomIconDrawer
CustomIconDrawer是自定义图标绘制器类,用于绘制自定义图标。它虽然也实现了IIconDrawer接口,但它并不用于共享。对应于享元模式的参与者,CustomIconDrawer是非共享具体享元UnsharedConcreteFlyweight。下面的代码给出了CustomIconDrawer的声明。

package demo.designpattern.flyweight;

/**
 * 自定义图标绘制器
 * Created by LiMingzi on 2017/7/24.
 */
public class CustomIconDrawer implements IIconDrawer {
    /**
     * 图标路径
     */
    private String iconUrl;
    /**
     * 横坐标
     */
    private int x;
    /**
     * 纵坐标
     */
    private int y;
    /**
     * 显示名称
     */
    private String name;

    /**
     * 获取图标路径
     * @return
     */
    public String getIconUrl() {
        return iconUrl;
    }

    /**
     * 获取横坐标
     * @return
     */
    public int getX() {
        return x;
    }

    /**
     * 获取纵坐标
     * @return
     */
    public int getY() {
        return y;
    }

    /**
     * 获取像是名称
     * @return
     */
    public String getName() {
        return name;
    }

    /**
     * 设置图标url
     * @param iconUrl 图标url
     */
    public void setIconUrl(String iconUrl) {
        this.iconUrl = iconUrl;
    }

    /**
     * 设置横坐标
     * @param x 横坐标
     */
    public void setX(int x) {
        this.x = x;
    }

    /**
     * 设置纵坐标
     * @param y 纵坐标
     */
    public void setY(int y) {
        this.y = y;
    }

    /**
     * 设置显示名称
     * @param name 显示名称
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 绘制图标
     *
     * @param x    横坐标
     * @param y    纵坐标
     * @param name 显示名称
     */
    @Override
    public void draw(int x, int y, String name) {
        System.out.println("在坐标("+x+","+y+")绘制了名为\""+name+"\"的自定义图标,url:"+iconUrl);
    }
}

从上述代码中可以看到,CustomIconDrawer维护了绘制图标所需要的全部状态。
4.5 IconDrawerFactory
IconDrawerFactory是图标绘制器工厂,用于生产图标绘制器。对应于享元模式的参与者,IconDrawerFactory是享元工厂FlyweightFactory。下面的代码给出了IconDrawerFactory的声明。

package demo.designpattern.flyweight;

import java.util.HashMap;
import java.util.Map;

/**
 * 图标绘制器工厂
 * Created by LiMingzi on 2017/7/24.
 */
public class IconDrawerFactory {
    /**
     * 实例
     */
    private static IconDrawerFactory ourInstance = new IconDrawerFactory();

    /**
     * 获取单例实例
     * @return 实例
     */
    public static IconDrawerFactory getInstance() {
        return ourInstance;
    }

    /**
     * 构造方法
     */
    private IconDrawerFactory(){

    }
    /**
     * 图标绘制器工厂
     */
    private Map iconDrawerMap = new HashMap<>();

    /**
     * 获取图标工厂
     * @param type 图标类型
     * @return 图标绘制器对象
     */
    public IIconDrawer getIconDrawer(String type){
        // 图标绘制器
        IIconDrawer iconDrawer;
        if(type.equals("custom")){
            // 自定义图标绘制器
            iconDrawer = new CustomIconDrawer();
        }else{
            if(iconDrawerMap.containsKey(type)){
                iconDrawer = iconDrawerMap.get(type);
            }else{
                iconDrawer = new IconDrawer(type);
                iconDrawerMap.put(type,iconDrawer);
            }
        }
        return iconDrawer;
    }
}

上述代码中,33行,成员变量iconDrawerMap作为图标绘制器享元池维护了已创建的图标绘制器。从43-53行可以看出,图标绘制器工厂根据图标类型生产对象,当图标类型为“custom”时一律创建CustomIconDrawer类型新对象,且不加入享元池;否则根据类型判断当前享元池中是否存在图标绘制器。如果存在则返回,不存在则创建新的IconDrawer对象返回,并将其加入享元池。
4.6 MapDrawer
MapDrawer是地图绘制器类,用于绘制地图中的色块、路线、文字及图标。对应于享元模式的参与者,MapDrawer是客户Client。下面的代码给出了MapDrawer的声明。

package demo.designpattern.flyweight;

import java.util.ArrayList;
import java.util.List;

/**
 * 地图绘制器
 * Created by LiMingzi on 2017/7/25.
 */
public class MapDrawer {
    /**
     * 图标绘制器(含图标信息)集合,Object[0]为IIconDrawer对象,Object[1]为横坐标,Object[2]为纵坐标,Object[3]为显示名,根据drawer,各item可为null
     */
    private List iconDrawersWithInfo = new ArrayList<>();

    /**
     * 载入图标信息
     * @param iconsInfo 图标信息集合,Object[0]为图标类型,Object[1]为横坐标,Object[2]为纵坐标,Object[3]为显示名,Object[4]为附加参数,当类型为“custom”时,其值为图标url
     */
    public void loadIcons(ListiconsInfo){
        // 图标绘制器工厂
        IconDrawerFactory iconDrawerFactory = IconDrawerFactory.getInstance();
        iconDrawersWithInfo.clear();
        for (Object[] iconInfo : iconsInfo) {
            // 图标绘制器(含信息)
            Object[] iconDrawerWithInfo = new Object[4];
            iconDrawerWithInfo[0] = iconDrawerFactory.getIconDrawer((String)iconInfo[0]);
            iconDrawerWithInfo[1]=iconInfo[1];
            iconDrawerWithInfo[2]=iconInfo[2];
            iconDrawerWithInfo[3]=iconInfo[3];
            if(iconInfo[0].equals("custom")){
                ((CustomIconDrawer)iconDrawerWithInfo[0]).setX((int)iconInfo[1]);
                ((CustomIconDrawer)iconDrawerWithInfo[0]).setY((int)iconInfo[2]);
                ((CustomIconDrawer)iconDrawerWithInfo[0]).setName((String) iconInfo[3]);
                ((CustomIconDrawer)iconDrawerWithInfo[0]).setIconUrl((String) iconInfo[4]);
            }
            iconDrawersWithInfo.add(iconDrawerWithInfo);
        }
    }

    /**
     * 绘制地图
     */
    public void draw(){
        // demo,清空画布
        System.out.println("清空画布");
        for (Object[] iconDrawerWithInfo : iconDrawersWithInfo)
        {
            // 图标绘制器
            IIconDrawer iconDrawer = (IIconDrawer) iconDrawerWithInfo[0];
            iconDrawer.draw((int)iconDrawerWithInfo[1],(int)iconDrawerWithInfo[2],(String) iconDrawerWithInfo[3]);
        }
    }
}

上述代码中,14行,成员变量iconDrawersWithInfo维护了包含外部信息的图标绘制器集合。在地图绘制应用中,往往采用“一次载入,多次绘制”的渲染策略。20行loadIcons方法载入外部传入的图标信息并将其缓存,44行draw方法绘制缓存的图标。
4.7 测试代码
为了测试本文中的代码,我们可以编写如下测试代码。注意,代码中的注释标注了哪些享元将被创建;哪些享元将被分享重用。

   /**
     * 享元测试
     */
    public static void flyweightTest(){
        // 图标信息集合
        ListiconsInfo = new ArrayList<>();
        // 建筑图标1(将创建新享元对象)
        Object[] buildingIconInfo1 = new Object[5];
        buildingIconInfo1[0]="building";
        buildingIconInfo1[1]=23;
        buildingIconInfo1[2]=45;
        buildingIconInfo1[3]="e世界大厦";
        iconsInfo.add(buildingIconInfo1);
        // 医院图标(将创建新享元对象)
        Object[] hospitalIconInfo = new Object[5];
        hospitalIconInfo[0]="hospital";
        hospitalIconInfo[1]=451;
        hospitalIconInfo[2]=33;
        hospitalIconInfo[3]="海淀医院";
        iconsInfo.add(hospitalIconInfo);
        // 家的位置(将创建新享元对象)
        Object[] homeIconInfo = new Object[5];
        homeIconInfo[0]="custom";
        homeIconInfo[1]=525;
        homeIconInfo[2]=124;
        homeIconInfo[3]="月泉路";
        homeIconInfo[4]="http://12.125.23.64/icons/house.ico";
        iconsInfo.add(homeIconInfo);
        // 建筑图标2(重用享元对象)
        Object[] buildingIconInfo2 = new Object[5];
        buildingIconInfo2[0]="building";
        buildingIconInfo2[1]=23;
        buildingIconInfo2[2]=45;
        buildingIconInfo2[3]="海龙大厦";
        iconsInfo.add(buildingIconInfo2);
        // 公司的位置(将创建新享元对象)
        Object[] companyIconInfo = new Object[5];
        companyIconInfo[0]="custom";
        companyIconInfo[1]=352;
        companyIconInfo[2]=85;
        companyIconInfo[3]="清华东路";
        companyIconInfo[4]="http://12.125.23.64/icons/company.ico";
        iconsInfo.add(companyIconInfo);
        // 左地图绘制器
        MapDrawer leftMapDrawer = new MapDrawer();
        leftMapDrawer.loadIcons(iconsInfo);
        // 第一次绘制
        leftMapDrawer.draw();
        System.out.println("----------------------------------------------------");
        // 第二次绘制
        leftMapDrawer.draw();
        System.out.println("----------------------------------------------------");
        // 右地图绘制器
        MapDrawer rightMapDrawer = new MapDrawer();
        rightMapDrawer.loadIcons(iconsInfo);
        rightMapDrawer.draw();
    }

编译运行后,得到如下测试结果:
清空画布
在坐标(23,45)绘制了名为”e世界大厦”的building类型图标
在坐标(451,33)绘制了名为”海淀医院”的hospital类型图标
在坐标(525,124)绘制了名为”月泉路”的自定义图标,url:http://12.125.23.64/icons/house.ico
在坐标(23,45)绘制了名为”海龙大厦”的building类型图标
在坐标(352,85)绘制了名为”清华东路”的自定义图标,url:http://12.125.23.64/icons/company.ico

———————————————————————–

清空画布
在坐标(23,45)绘制了名为”e世界大厦”的building类型图标
在坐标(451,33)绘制了名为”海淀医院”的hospital类型图标
在坐标(525,124)绘制了名为”月泉路”的自定义图标,url:http://12.125.23.64/icons/house.ico
在坐标(23,45)绘制了名为”海龙大厦”的building类型图标
在坐标(352,85)绘制了名为”清华东路”的自定义图标,url:http://12.125.23.64/icons/company.ico

———————————————————————–
清空画布
在坐标(23,45)绘制了名为”e世界大厦”的building类型图标
在坐标(451,33)绘制了名为”海淀医院”的hospital类型图标
在坐标(525,124)绘制了名为”月泉路”的自定义图标,url:http://12.125.23.64/icons/house.ico
在坐标(23,45)绘制了名为”海龙大厦”的building类型图标
在坐标(352,85)绘制了名为”清华东路”的自定义图标,url:http://12.125.23.64/icons/company.ico

5 扩展

5.1 单例的使用
示例代码中,IconDrawerFactory类被实现为单例。在实际项目中,多个设计模式结合使用是非常常见的。但本系列教程中,为了降低代码复杂度,通常不会在一组示例代码中出现多个设计模式。本例中如此使用,是为了让读者明确享元的共享范围不仅在单个客户内,也可以在多个客户间。
5.2 非分享享元的创建
非分享享元虽然不用于分享,但从功能划分的角度,仍然应该由享元工厂创建。本讲示例代码中,为了保持代码的规整,虽然非分享享元已经维护了全部状态,但在客户代码中,仍然维护了其外部状态。希望读者能够理解,在实际的业务代码中,是不会这样“简单粗暴”处理的。

你可能感兴趣的:(算法与程序设计,设计模式,java,架构设计,设计模式讲解与代码实践)