十八:享元模式(共享重复对象,分离不同对象)

Posted 2019lgg

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了十八:享元模式(共享重复对象,分离不同对象)相关的知识,希望对你有一定的参考价值。

定义:享元模式(英语:Flyweight Pattern)是一种软件设计模式。它使用共享物件,用来尽可能减少内存使用量以及分享资讯给尽可能多的相似物件;它适合用于当大量物件只是重复因而导致无法令人接受的使用大量内存。通常物件中的部分状态是可以分享。常见做法是把它们放在外部数据结构,当需要使用时再将它们传递给享元。

 “看定义的意思,这个模式主要是为了减少不必要的重复对象,减少内存消耗。而要做到这个的话,那么就需要把一个对象可共享的状态给封装起来,而不可能共享的状态则从外部获取。”

小左指尖不停的敲打着桌面,继续说道:“如果所有DOTA的玩家都在一台服务器上操作,那么DOTA里的英雄应该就可以使用享元模式。每一个英雄应该都是唯一的实例,而不应该是一旦有人选了一个英雄,就要实例化一个。”

“如果是传统意义上的方式,应该是这种写法。假设有一个抽象的英雄基类。”

技术图片
package com.flyweight;

//抽象英雄基类
public abstract class AbstractHero 
    
    protected final String name;//英雄名称
    
    protected final String[] skills = new String[4];//每个英雄都有四个技能
    
    protected long hp;//血量
    
    protected long mp;//魔法
    
    public AbstractHero() 
        super();
        this.name = getName();
        initSkills();
        checkSkills();
    
    
    private void checkSkills()
        for (int i = 0; i < skills.length; i++) 
            if (skills[i] == null) 
                throw new NullPointerException();
            
        
    
    
    //释放技能
    public void release(int index)
        if (index < 0) 
            index = 0;
        else if (index > 3) 
            index = 3;
        
        System.out.println(name + "释放" + skills[index]);
     
    
    //物理攻击
    public void commonAttack()
        System.out.println(name + "进行物理攻击");
    
    
    public abstract String getName();
    
    public abstract void initSkills();

    public long getHp() 
        return hp;
    

    public void setHp(long hp) 
        this.hp = hp;
    

    public long getMp() 
        return mp;
    

    public void setMp(long mp) 
        this.mp = mp;
    
    
技术图片

                “英雄可以释放技能,物理攻击等等,而且英雄是有血量和魔法量的。下面就写两个具体的英雄看一下。”

技术图片
package com.flyweight;

public class Lion extends AbstractHero

    public String getName() 
        return "恶魔巫师";
    

    public void initSkills() 
        skills[0] = "穿刺";
        skills[1] = "妖术";
        skills[2] = "法力汲取";
        skills[3] = "死亡一指";
    

技术图片
技术图片
package com.flyweight;

public class SF extends AbstractHero

    public String getName() 
        return "影魔";
    

    public void initSkills() 
        skills[0] = "毁灭阴影";
        skills[1] = "支配死灵";
        skills[2] = "魔王降临";
        skills[3] = "魂之挽歌";
    

技术图片

                “一个恶魔巫师,一个影魔。很显然,假设现在有四个solo局,都是SF对LION,那么现在这种方式则需要实例化四个LION和四个SF。就像下面这样。”

技术图片
package com.flyweight;

public class Main 

    public static void main(String[] args) 
        //假设有四个solo局,则需要创建四个lion和四个sf
        Lion lion1=new Lion();
        SF sf1 = new SF();
        
        Lion lion2=new Lion();
        SF sf2 = new SF();
        
        Lion lion3=new Lion();
        SF sf3 = new SF();
        
        Lion lion4=new Lion();
        SF sf4 = new SF();
        
        /* 以下为释放技能,物理攻击等的打架过程  */
    
    
技术图片

                “这样显然是非常浪费资源啊,四个lion和四个sf其实是有很多一样的地方的。这应该就可以使用享元模式了吧。”

                “不过享元模式强调内部状态和外部状态,内部状态则是可以共享的状态,外部状态则是随外部环境而变化的状态,是无法共享的状态。那么下面要做的就是将外部状态分离出来,只保留内部状态,这样的话对象的实例就可以共享了。”

                “啪!”

                小左打了个响指,继续说道:“对于上面DOTA英雄的简单例子来说,血量和魔法量应该是外部状态了,因为四个solo局中,每个lion和每个sf的血量和魔法量不一定是相等的,所以这两个状态是无法共享的。但是技能和名称就不一样了,它们俩应该属于内部状态,是可以共享的,因为不管是哪个solo局里的lion或者sf,它们的英雄名称和技能都是一样的。”

                “这下好办了,首先应该把基类里面的血量和魔法量删掉。”

技术图片
package com.flyweight;

//抽象英雄基类
public abstract class AbstractHero 
    
    protected final String name;//英雄名称
    
    protected final String[] skills = new String[4];//每个英雄都有四个技能
    
    public AbstractHero() 
        super();
        this.name = getName();
        initSkills();
        checkSkills();
    
    
    private void checkSkills()
        for (int i = 0; i < skills.length; i++) 
            if (skills[i] == null) 
                throw new NullPointerException();
            
        
    
    
    //释放技能
    public void release(int index)
        if (index < 0) 
            index = 0;
        else if (index > 3) 
            index = 3;
        
        System.out.println(name + "释放" + skills[index]);
     
    
    //物理攻击
    public void commonAttack()
        System.out.println(name + "进行物理攻击");
    
    
    public abstract String getName();
    
    public abstract void initSkills();
    
技术图片

               “其余的两个子类应该是不用变的,下面我还需要再写一个类去组合英雄的内部状态和外部状态。”

技术图片
package com.flyweight;
//用于组合英雄内部状态和外部状态的类,假设称为角色
public class Role 

    private AbstractHero hero;//角色选择的英雄
    
    //我们把血量和魔法量这两个外部状态从英雄里剥离出来,放到外部的角色类中
    private long hp;
    
    private long mp;

    public Role(AbstractHero hero) 
        super();
        this.hero = hero;
    
    
    //释放技能
    public void release(int index)
        hero.release(index);
     
    
    //物理攻击
    public void commonAttack()
        hero.commonAttack();
    

    public long getHp() 
        return hp;
    

    public void setHp(long hp) 
        this.hp = hp;
    

    public long getMp() 
        return mp;
    

    public void setMp(long mp) 
        this.mp = mp;
    
    
技术图片

                “我们用角色这个类去组合英雄的内部和外部状态,下面还需要一个享元模式最重要的类,就是提供共享功能的类。”

技术图片
package com.flyweight;

import java.util.HashMap;
import java.util.Map;
//提供共享功能,单例
public class HeroManager 
    
    private static HeroManager heroManager = new HeroManager();

    private Map<String, AbstractHero> heroMap;
    
    private HeroManager()
        heroMap = new HashMap<String, AbstractHero>();
    
    
    public static HeroManager getInstance()
        return heroManager;
    
    
    //该方法提供共享功能
    public AbstractHero getHero(String name)
        AbstractHero hero = heroMap.get(name);
        if (hero == null) 
            if (name.equals("恶魔巫师")) 
                hero = new Lion();
            else if (name.equals("影魔")) 
                hero = new SF();
            
            heroMap.put(name, hero);
        
        return hero;
    
技术图片

                “现在如果再来四个solo局,那么情况就和刚才不太一样了。”

技术图片
package com.flyweight;

public class Main 

    public static void main(String[] args) 
        //假设有四个solo局,则使用了享元模式的情况下,其实恶魔巫师和影魔的实例各自只有一个
        HeroManager heroManager = HeroManager.getInstance();
        Role role1 = new Role(heroManager.getHero("恶魔巫师"));
        Role role2 = new Role(heroManager.getHero("影魔"));
        
        Role role3 = new Role(heroManager.getHero("恶魔巫师"));
        Role role4 = new Role(heroManager.getHero("影魔"));
        
        Role role5 = new Role(heroManager.getHero("恶魔巫师"));
        Role role6 = new Role(heroManager.getHero("影魔"));
        
        Role role7 = new Role(heroManager.getHero("恶魔巫师"));
        Role role8 = new Role(heroManager.getHero("影魔"));
        
        /* 以下为释放技能,物理攻击等的打架过程  */
    
    
技术图片

                “四个solo局当中有八个角色,选了八个英雄,四个lion和四个sf,但是很明显,使用了享元模式之后,这里面的八个英雄其实只有一个lion实例和一个sf实例,这样就大大减少了英雄的实例个数。试想一下,如果同时有1000个solo局,按照之前的方式,那么会有1000个lion的实例和1000个sf的实例,而现在使用享元模式的话,依旧是只有一个lion实例和一个sf实例。”

以上是关于十八:享元模式(共享重复对象,分离不同对象)的主要内容,如果未能解决你的问题,请参考以下文章

设计模式——享元模式

享元模式

设计模式享元模式(*)

设计模式享元模式

享元模式

设计模式 --面试高频之享元模式