zoukankan      html  css  js  c++  java
  • 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[]内容。

     

  • 相关阅读:
    【计算机图形学】变换 (Transform)
    [图像处理]基于 PyTorch 的高斯核卷积
    [PyTorch] torch.squeee 和 torch.unsqueeze()
    【图像分析】边缘检测中的图像梯度以及各种梯度算子的介绍
    你买的课程看了吗?
    为什么用抓包工具看HTTPS包是明文的
    定制化Fiddler
    软件测试常见网络相关面试题
    单线程和多线程执行对比—Python多线程编程
    多线程实践—Python多线程编程
  • 原文地址:https://www.cnblogs.com/danielleee/p/8619593.html
Copyright © 2011-2022 走看看