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

定义:享元模式(英语: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实例。”

原文地址:https://www.cnblogs.com/lgg20/p/11097608.html