12-五子棋游戏:享元模式

12.1 五子棋游戏

  本章以五子棋游戏为例,来学习享元模式。

12.2 模式定义

  享元模式(Flyweight Pattern),以共享的方式高效地支持大量的细粒度对象。通过复用内存中已经存在的对象,降低系统创建对象实例的性能消耗。享元的英文是Flyweight,它是一个来自于体育方面的专业术语,在拳击、摔跤和举重比赛中特指最轻量的级别。把这个单词移植到软件工程里,也是用来表示告别小的对象,即细粒度对象。至于为什么把Flyweight翻译为“享元”,可以理解为共享元对象,也就是共享细粒度对象。

  在面向对象的眼中,万事万物一切皆对象。但是不可避免的是,采用面向对象的编程方式,可能会增加一些资源和性能上的开销。不过,在大多数情况下,这种影响还不是太大,所以,它带来的空间和性能上的损耗相对于它的优点而言,基本上不用考虑。但是,在某些特殊情况下,大量细粒度对象的创建、销毁及存储所造成的资源和性能上的损耗,可能会在系统运行时形成瓶颈。那么该如何避免产生大量的细粒度对象,同时又不影响系统使用面向对象的方式进行操作呢?享元模式提供了一个比较好的解决方案。

12.3 模式分析

  下面,我们来分析上面的五子棋游戏。

  1)需要一个抽象棋子类,黑子和白子继承该类;

  2)需要一个获得棋子的工厂,用来获得棋子对象,并且缓存棋子对象实例,不至于每次都是new一个棋子出来。

  享元模式的要义就在于“避免产生大量的细粒度对象”。

  在棋子工厂的缓存中,如果存在棋子对象内容,则使用缓存当中的对象实例;如果不存在,则创建一个新的棋子对象,缓存之后返回。一般情况下,我们把工厂设置为单例模式,使用HashTable作为工厂的缓存结构,缓存中以“B”和“W”字母作为key,value中则存储“黑子”和“白子”对象实例。

12.4 模式实现

12.4.1 创建抽象棋子

package com.demo.flyweight.factory;

/**
 * Created by lsq on 2018/3/20.
 * 抽象棋子类
 */
public abstract class AbstractChessman {

    //棋子类别(黑|白)
    protected String chess;

    public AbstractChessman(String chess){
        this.chess = chess;
    }

    //显示棋子信息
    public void show(){
        System.out.println(this.chess);
    }

}

12.4.2 棋子实现

1. 黑子实现——BlackChessman

package com.demo.flyweight.factory;

/**
 * Created by lsq on 2018/3/20.
 * 黑子实现类
 */
public class BlackChessman extends AbstractChessman{

    /**
     * 构造方法:初始化黑棋子
     */
    public BlackChessman() {
        super("○");
        System.out.println("---BlackChessman Constructor Execute!");
    }
}

2. 白子实现——WhiteChessman

package com.demo.flyweight.factory;

/**
 * Created by lsq on 2018/3/20.
 * 白子实现类
 */
public class WhiteChessman extends AbstractChessman{

    /**
     * 构造方法,初始化白棋子
     */
    public WhiteChessman() {
        super("●");
        System.out.println("---WhiteChessman Constructor Execute!");
    }
}

12.4.3 创建棋子工厂

package com.demo.flyweight.factory;

import java.util.Hashtable;

/**
 * Created by lsq on 2018/3/20.
 * 棋子工厂
 */
public class FiveChessmanFactory {

    //单例模式工厂
    private static FiveChessmanFactory fiveChessmanFactory = new FiveChessmanFactory();

    //缓存存放共享对象
    private final Hashtable<Character, AbstractChessman> cache = new Hashtable<>();

    //私有化构造方法
    private FiveChessmanFactory(){}

    //获得单例工厂
    public static FiveChessmanFactory getInstance(){
        return fiveChessmanFactory;
    }

    /**
     * 根据字符获取棋子
     * @param c (B:围棋,W:白棋)
     * @return
     */
    public AbstractChessman getChessmanObject(char c){
        //从缓存中获得棋子对象实例
        AbstractChessman abstractChessman = this.cache.get(c);
        if (abstractChessman == null){
            //缓存中没有棋子对象实例信息,则创建棋子对象实例,并放入缓存
            switch (c){
                case 'B':
                    abstractChessman = new BlackChessman();
                    break;
                case 'W':
                    abstractChessman = new WhiteChessman();
                    break;
                default:
                    break;
            }

            //为防止非法字符的进入,返回null
            if (abstractChessman != null){
                //放入缓存
                this.cache.put(c, abstractChessman);
            }
        }
        return abstractChessman;
    }
}

12.4.4 客户端测试

package com.demo.flyweight;

import com.demo.flyweight.factory.AbstractChessman;
import com.demo.flyweight.factory.FiveChessmanFactory;

import java.util.Random;

/**
 * Created by lsq on 2018/3/21.
 * 应用程序
 */
public class Client {

    public static void main(String[] args) {
        //创建五子棋工厂
        FiveChessmanFactory fiveChessmanFactory = FiveChessmanFactory.getInstance();
        //随机数,用来随机生成棋子对象
        Random random = new Random();
        int radom = 0;
        AbstractChessman abstractChessman = null;
        //随机获得棋子
        for (int i=0;i<10;i++){
            radom = random.nextInt(2);
            switch (radom){
                //获得黑棋
                case 0:
                    abstractChessman = fiveChessmanFactory.getChessmanObject('B');
                    break;
                //获得白棋
                case 1:
                    abstractChessman = fiveChessmanFactory.getChessmanObject('W');
                    break;
            }
            if (abstractChessman != null){
                abstractChessman.show();
            }
        }
    }

}

运行结果:

  从上面的运行结果可以看到,“黑子”和“白子”的构造方法分别只被执行了一次,其余的都是从缓存中获得的对象实例。

12.4.5 如何实现棋子的位置

  我们的享元对象已经生效了,共享了元对象,节省了内存空间。然而,我们现在只做到了共享棋子对象实例,还有一点不要忘记,那就是棋子的位置。虽然棋子对象是可以共享的,但是,每一个棋子的位置都是不一样的,是不能共享的。这也就是享元模式的两种状态:内蕴状态(Internal State)和外蕴状态(External State)。

  1)内蕴状态

  享元对象的内蕴状态是不会随着环境的改变而改变的,在存储在享元对象内部的状态信息,因此内蕴状态是可以共享的,对于任何一个享元对象来讲,它的值是完全相同的。就像上面例子中的“黑子”和“白子”,它代表的状态就是内蕴状态。

  2)外蕴状态

  外蕴状态会随环境的改变而改变,因此是不可以共享的状态,对于不同的享元对象来讲,它的值可能是不同的。享元对象的外蕴状态必须由客户端保存,在享元对象被创建之后,需要使用的时候再传入到享元对象内部。就像五子棋的位置信息,代表的状态就是享元对象的外蕴状态。

  享元模式的两种状态是相互独立的,彼此没有关联。

  3)实现棋子的外蕴状态——位置

  首先,我们需要声明的一点是,虽然外蕴状态的内容是会随着环境改变而改变的,但是外蕴状态变量还是需要的,我们需要在抽象棋子类中增加棋子位置信息,以及设置位置的方法内容。

  下面,我们就在抽象棋子类中增加棋子位置信息x,y,同时增加设置棋子位置的方法point,在显示棋子信息的方法中增添棋子的位置信息。修改后的AbstractChessman抽象棋子类内容如下所示:

package com.demo.flyweight.object;

/**
 * Created by lsq on 2018/3/20.
 * 抽象棋子类
 */
public abstract class AbstractChessman {

    //棋子坐标
    protected int x,y;

    //棋子类别(黑|白)
    protected String chess;

    public AbstractChessman(String chess){
        this.chess = chess;
    }

    //点坐标设置
    public abstract void point(int x, int y);

    //显示棋子信息
    public void show(){
        System.out.println(this.chess+"("+this.x+","+this.y+")");
    }

}

完美黑子类“BlackChessman”,实现父类抽象方法point,内容如下所示:

package com.demo.flyweight.object;


/**
 * Created by lsq on 2018/3/20.
 * 黑子实现类
 */
public class BlackChessman extends AbstractChessman {

    /**
     * 构造方法:初始化黑棋子
     */
    public BlackChessman() {
        super("○");
        System.out.println("---BlackChessman Constructor Execute!");
    }

    /**
     * 实现父类方法,设置位置信息
     */
    @Override
    public void point(int x, int y) {
        this.x = x;
        this.y = y;
        this.show();
    }
}

完美白子类“WhiteChessman”,实现父类抽象方法point。

package com.demo.flyweight.object;

/**
 * Created by lsq on 2018/3/20.
 * 白子实现类
 */
public class WhiteChessman extends AbstractChessman {

    /**
     * 构造方法,初始化白棋子
     */
    public WhiteChessman() {
        super("●");
        System.out.println("---WhiteChessman Constructor Execute!");
    }

    /**
     * 实现父类方法,设置位置信息
     */
    @Override
    public void point(int x, int y) {
        this.x = x;
        this.y = y;
        this.show();
    }
}

12.4.6 测试棋子的外蕴状态

package com.demo.flyweight;
import com.demo.flyweight.object.AbstractChessman;
import com.demo.flyweight.object.FiveChessmanFactory;

import java.util.Random;

/**
 * Created by lsq on 2018/3/21.
 * 应用程序
 */
public class Client2 {

    public static void main(String[] args) {
        //创建五子棋工厂
        FiveChessmanFactory fiveChessmanFactory = FiveChessmanFactory.getInstance();
        //随机数,用来随机生成棋子对象
        Random random = new Random();
        int radom = 0;
        AbstractChessman abstractChessman = null;
        //随机获得棋子
        for (int i=0;i<10;i++){
            radom = random.nextInt(2);
            switch (radom){
                //获得黑棋
                case 0:
                    abstractChessman = fiveChessmanFactory.getChessmanObject('B');
                    break;
                //获得白棋
                case 1:
                    abstractChessman = fiveChessmanFactory.getChessmanObject('W');
                    break;
            }
            if (abstractChessman != null){
                //设置棋子位置信息(x:0~9,y:0~15的随机数产生)
                abstractChessman.point(i, random.nextInt(15));
            }
        }
    }

}

运行结果如下:

  享元模式的重点在于共享元对象,降低内存的使用空间,提高系统性能。享元对象的外蕴状态是通过客户端来保存传入的,它是可能发生变化的。因此,在我们进行软件系统设计的时候,一定要区分享元对象的内蕴状态和外蕴状态,不能混淆,更不能互相关联,二者应是彼此分开的。

12.5 使用场合

  1)当系统中某个对象类型的实例较多的时候;

  2)在系统设计中,对象实例进行分类后,发现真正有区别的分类很少的时候。

扩展1:Java SDK中的享元模式

  享元模式是系统中经常用到的,特别是对于细粒度对象比较多的软件系统,使用起来非常有效,不但可以提高系统内在空间,还可以提高系统效率。在JDK中也存在着享元模式的身影,如java.lang.Integer。java.lang.Integer类中的valueOf方法就是享元模式的具体应用。在该方法中,首先判断参数的范围,如果在缓存范围内,则返回缓存中的内容,否则创建一个新对象返回。而缓存类IntegerCache作为java.lang.Integer类的内部类,已经在类初始化的时候,设置了Integer数组缓存cache[]内容。

 

 

posted @ 2018-03-21 20:58  shanquan  阅读(799)  评论(0编辑  收藏  举报