从王者荣耀看设计模式(十九.享元模式)

从王者荣耀看设计模式(享元模式)

一.简介

在王者荣耀的经典对战模式中,小兵是不可或缺的组成部分。英雄通过击杀小兵可获取升级的所需的经验和购买装备所需的金钱。小兵可根据类型分为有远程小兵和近战小兵等。此外,游戏中红色阵营和蓝色阵营的小兵也会有些许不同。

二.模式动机

在很多情况下需要在系统中添加类和对象的个数。当对象的数量太多时,将导致运行代价过高,带来性能下降等问题。享元模式正是解决这一问题而诞生的。享元模式通过共享技术实现相同或相似对象的重用,在逻辑上每一个出现的对象都有一个对象与之对应,然而在物理上它们共享同一个享元对象。存储享元对象实例的对象可以称为享元池
在实现享元模式时需要注意两个问题:第一是享元类的设计,在系统中有些对象并不完全相同,他们只是相似,因此首先需要找出这些对象的共同点,在享元类中封装这些共同的内容,对于不同的内容可以通过外部应用程序来设置,而不进行共享,在享元模式中这些可以共享的相同内容称为内部状态(Intrinsic State),而那些需要外部环境来设置的不能共享的内容称为外部状态(Extrinsic State),由于区分了内部状态和外部状态,因此可以通过设置不同的外部状态使得相同的对象可以具有一些不同的特征,而相同的内部状态是可以共享的 第二个问题是享元对象的存放,在享元对象中通常会出现工厂模式,需要创建一个享元工厂来负责维护一个享元池(Flyweight Pool)用于存储具有相同内部状态的享元对象

三.享元模式

享元模式(Flyweight Pattern):运用共享技术有效地支持大量细颗粒对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细颗粒对象,因此它又称为轻量级模式,它是一种对象结构型模式

享元模式的使用场景
在以下情况下可以使用享元模式
■一个系统有大量相同或者相似的对象,由于这类对象的大量使用,造成内存的大量耗费。
■对象的大部分状态都可以外部化,可以将这些外部状态传到对象中。
■使用享元模式需要维护一个存储享元对象的享元池,而这需要耗费资源,因此,应当在多次重复使用享元对象时才值得使用享元模式。

享元模式涉及的设计模式有:
★ 封装变化(封装了工厂模式)
★ 类对于扩展开放,对修改关闭

享元模式通用类图

享元模式涉及的角色:
享元模式包含以下角色:
Flyweight(抽象享元类)
抽象享元类声明一个接口,通过它可以接受并作用于外部状态。在抽象享元类中定义了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)
ConcreteFlyweight(具体享元类)
具体享元类实现了抽象享元接口,其实例称为享元对象;在具体享元类中为内部状态提供了存储空间,由于具体享元对象必须是可以共享的,因此它所存储的状态必须是内部的,即它独立存在自己的环境中。可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象
UnsharedConcreteFlyweight(非共享具体享元类)
并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类则设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以通过直接实例化创建;在某些享元模式的层次结构中,非共享具体享元类还可以将具体享元对象作为子节点
FlyweightFactory(享元工厂类)
享元工厂类用于创建并管理享元对象;它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储键值对的集合(也可以是其他集合类型),可以结合工厂模式进行设计;当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),返回该新创建的实例,并将其存储在享元池中。

享元模式优点:
⑴.享元模式的优点在于它可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份
⑵.享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享

享元模式缺点:
⑴.使得系统更加复杂,需要分离出内部状态和外部状态,这使得程序逻辑复杂化
⑵.为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长

四.享元模式经典实例

享元模式经典类图见上图(・ω<)☆
创建Flyweight(抽象享元类)

package com.classmodule.flyweight;
/*
 * 创建抽象享元类
 */
public abstract class Flyweight {
    //内部状态
    public String intrinsic;
    //外部状态
    public String extrinsic;
    
    //要求享元角色必须接受外部状态
    public Flyweight(String extrinsic) {
        this.extrinsic = extrinsic;
    }
    
    //定义业务操作
    public abstract void operate(int extrinsic);
    
    public String getInstrinsic() {
        return intrinsic;
    }
    
    public void setIntrinsic(String _intrinsic) {
        this.intrinsic = _intrinsic;
    }
}

创建ConcreteFlyweight类(具体享元类)

package com.classmodule.flyweight;
/*
 * 创建ConcreteFlyweight类(具体享元类)
 */
public class ConcreteFlyweight extends Flyweight{
    //接受外部状态
    public ConcreteFlyweight(String extrinsic) {
        super(extrinsic);
    }
    
    //根据外部状态进行逻辑处理
    @Override
    public void operate(int extrinsic) {
        System.out.println("具体Flyweight:" + extrinsic);
    }
}

创建UnsharedConcreteFlyweight(非共享具体享元类)

package com.classmodule.flyweight;
/*
 * 非共享具体享元类
 */
public class UnsharedConcreteFlyweight extends Flyweight{
    public UnsharedConcreteFlyweight(String extrinsic) {
        super(extrinsic);
    }

    @Override
    public void operate(int extrinsic) {
        System.out.println("非共享的具体Flyweight:" + extrinsic);
    }
}

创建Flyweight类(享元工厂类)

package com.classmodule.flyweight;

import java.util.HashMap;
/*
 * 创建享元工厂类
 */
public class FlyweightFactory {
    //定义一个池容器
    private static HashMap<String,Flyweight> pool = new HashMap<>();
    
    //享元工厂
    public static Flyweight getFlyweight(String extrinsic) {
        Flyweight flyweight = null;
        
        if(pool.containsKey(extrinsic)) {//池中有该对象
            flyweight = pool.get(extrinsic);
            System.out.print("已有 " + extrinsic + "直接从池中取出--->");
        }else {
            //根据外部状态创建享元对象
            flyweight = new ConcreteFlyweight(extrinsic);
            //放入池中
            pool.put(extrinsic, flyweight);
            System.out.print("创建" + extrinsic + "并从池中取出---->");
        }
        return flyweight;
    }
}

创建Client类(测试类)

package com.practice.Client;

import com.classmodule.flyweight.Flyweight;
import com.classmodule.flyweight.FlyweightFactory;

public class Client {
    public static void main(String[] args) {
        int extrinsic = 22;
        
        Flyweight flyweightX = FlyweightFactory.getFlyweight("X");
        flyweightX.operate(++extrinsic);
    
        Flyweight flyweightY = FlyweightFactory.getFlyweight("Y");
        flyweightY.operate(++extrinsic);
        
        Flyweight flyweightZ = FlyweightFactory.getFlyweight("Z");
        flyweightZ.operate(++extrinsic);
        
        Flyweight flyweightReX = FlyweightFactory.getFlyweight("X");
        flyweightReX.operate(++extrinsic);
    }
}

运行结果:

结果分析:共享池中创建存储了X,Y,Z三个对象,当Client再次向享元工厂请求X对象时,享元工厂从共享池中返回已经存在的对象实例。

五.无外部状态享元类

小兵有远程兵和超级兵。同种类型的小兵类型描述应该是一样的。
设计类图

创建抽象类(Soldier类)
Soldier类是抽象享元类,声明了所有具体享元类共有的方法

package com.practice.Flyweight;
/*
 * 创建抽象类(Soldier类):声明所有具体享元类共有的方法
 */
public interface Soldier {
    public abstract void funcDescription(Camp belongCamp);
}

创建具体享元类(LongRangeSoldier类)
LongRangeSoldier类是具体享元类,它实现了在抽象享元类中声明的方法。在LongRangeSoldier中声明了属性type,实例化时给该type赋值,相同的LongRangeSoldier对象其type值一定相同,因此type是享元类LongRangeSoldier类中可共享的内部状态

package com.practice.Flyweight;
/*
 * 创建具体享元类
 * @param type 共享的内部状态,相同LongRangeSoldier对象,type值一定相同。
 */
public class LongRangeSoldier implements Soldier {
    private String type;
    
    public LongRangeSoldier(String _type) {
        this.type = _type;
    }
    
    public String getType() {
        return this.type;
    }
    
    @Override
    public void typeDescription() {
        System.out.println("小兵分类:远程兵,类型为:" + type);
    }

}

创建具体享元类(ShortRangeSoldier类)
ShortRangeSoldier类是具体享元类,它实现了在抽象享元类中声明的方法。在ShortRangeSoldier中声明了属性type,实例化时给该type赋值,相同的ShortRangeSoldier对象其type值一定相同,因此type是享元类ShortRangeSoldier类中可共享的内部状态

package com.practice.Flyweight;
/*
 * 创建具体享元类
 * @param type 共享的内部状态,相同ShortRangeSoldier对象,type值一定相同。
 */
public class ShortRangeSoldier implements Soldier {
    private String type;
    
    public ShortRangeSoldier(String _type) {
        this.type = _type;
    }
    
    public String getType() {
        return this.type;
    }
    
    @Override
    public void typeDescription() {
        System.out.println("小兵分类:近战兵,类型为:" + type);
    }
}

创建共享工厂类(SoldierFactory类)
SoldierFactory是享元工厂类,在SoldierFactory中定义了一个ArrayList类型soldier,用于存储多个具体享元对象,它是一个享元池,也可以使用HashMap实现,在SoldierFactory类中还提供了工厂方法getSoldierCategory(),用来根据所传入的参数返回享元池中的享元对象

package com.practice.Flyweight;

import java.util.ArrayList;
/*
 * 创建共享工厂类(SoldierFactory类)
 */
public class SoldierFactory {
    private ArrayList<Soldier> soldier = new ArrayList<>();
    private int totalSoldier = 0;
    public SoldierFactory() {
        Soldier s1 = new LongRangeSoldier("法师(使用魔法弹攻击敌人,属于魔法攻击)");
        soldier.add(s1);
        Soldier s2 = new ShortRangeSoldier("超级兵(使用机甲攻击敌人,属于物理攻击)");
        soldier.add(s2);
    }
    
    //获得小兵的类型
    public Soldier getSoldierCategory(String type) throws Exception {
        if(type.equals("法师")) {
            totalSoldier ++;
            return (Soldier)soldier.get(0);
        }else if(type.equals("超级兵")) {
            totalSoldier ++;
            return (Soldier)soldier.get(1);
        }else {
            throw new Exception();
        }
    }
    
    //获得小兵总数
    public int getSoldierCount() {
        return totalSoldier;
    }
    //获得小兵分类总数
    public int getSoldierType() {
        return soldier.size();
    }
}

创建客户测试类(Client类)

package com.practice.Client;

import com.practice.Flyweight.Soldier;
import com.practice.Flyweight.SoldierFactory;

public class Client {
    public static void main(String [] args) throws Exception {
        Soldier fx,fy,fz,fa,fb,fc;
        SoldierFactory factory = new SoldierFactory();
        
        fx = factory.getSoldierCategory("法师");
        fx.typeDescription();
        
        fy = factory.getSoldierCategory("超级兵");
        fy.typeDescription();
        
        fz = factory.getSoldierCategory("超级兵");
        fz.typeDescription();
        
        fa = factory.getSoldierCategory("超级兵");
        fa.typeDescription();
        
        fb = factory.getSoldierCategory("法师");
        fb.typeDescription();
        
        fc = factory.getSoldierCategory("法师");
        fc.typeDescription();
        
        System.out.println("小兵分类总数:" + factory.getSoldierType());
        System.out.println("小兵总数为:" + factory.getSoldierCount());   
    }
}

运行结果:

结果分析:在客户端代码中定义了6个Soldier对象,即享元对象,并且实例化了享元工厂SoldierFactory,通过SoldierFactory类中的工厂方法返回享元对象。调用了每一个享元对象的typeDescription()方法。Soldier类型的type是内部状态,可以共享,相同小兵类型其type一定相同

六.有外部状态的享元类

游戏存在里两个红、蓝两个阵营。小兵应该标识阵营
设计类图

创建抽象享元类(Soldier类)
Soldier类是抽象享元类,声明了所有具体享元类共有的方法

package com.practice.Flyweight;
/*
 * 创建抽象类(Soldier类):声明所有具体享元类共有的方法
 */
public interface Soldier {
    public abstract void funcDescription(Camp belongCamp);
}

创建外部状态(Camp)
用来标识小兵所属阵营

package com.practice.Flyweight;
/*
 *创建外部状态(Camp),用于标识小兵所属阵营
*/
public class Camp {
    private String belongCamp;
    
    public Camp(String _belongCamp) {
        this.belongCamp = _belongCamp;
    }
    
    public String getCamp() {
        return belongCamp;
    }
}

创建具体享元类(LongRangeSoldier类)

package com.practice.Flyweight;
/*
 * 创建具体享元类
 * @belongCamp接收外部状态
 */
public class LongRangeSoldier implements Soldier {
    private String type;
    
    public LongRangeSoldier(String _type) {
        this.type = _type;
    }
    
    public String getType() {
        return this.type;
    }

    @Override
    public void funcDescription(Camp belongCamp) {
        System.out.println("小兵分类:远程兵,类型为:" + type + ":" + belongCamp.getCamp());    
    }
}

创建具体享元类(ShortRangeSoldier类)

package com.practice.Flyweight;
/*
 * 创建具体享元类
 * @param belongCamp接受外部状态
 */
public class ShortRangeSoldier implements Soldier {
    private String type;
    
    public ShortRangeSoldier(String _type) {
        this.type = _type;
    }
    
    public String getType() {
        return this.type;
    }

    @Override
    public void funcDescription(Camp belongCamp) {
        System.out.println("小兵分类:近战兵,类型为:" + type + ":" + belongCamp.getCamp());
    }
}

创建享元工厂类(SoldierFactory类)

package com.practice.Flyweight;

import java.util.ArrayList;
/*
 * 创建共享工厂类(SoldierFactory类)
 */
public class SoldierFactory {
    private ArrayList<Soldier> soldier = new ArrayList<>();
    private int totalSoldier = 0;
    public SoldierFactory() {
        Soldier s1 = new LongRangeSoldier("法师(使用魔法弹攻击敌人,属于魔法攻击)");
        soldier.add(s1);
        Soldier s2 = new ShortRangeSoldier("超级兵(使用机甲攻击敌人,属于物理攻击)");
        soldier.add(s2);
    }
    
    //获得小兵的类型
    public Soldier getSoldierCategory(String type) throws Exception {
        if(type.equals("法师")) {
            totalSoldier ++;
            return (Soldier)soldier.get(0);
        }else if(type.equals("超级兵")) {
            totalSoldier ++;
            return (Soldier)soldier.get(1);
        }else {
            throw new Exception();
        }
    }
    
    //获得小兵总数
    public int getSoldierCount() {
        return totalSoldier;
    }
    //获得小兵分类总数
    public int getSoldierType() {
        return soldier.size();
    }
}

创建客户测试类(Client)

package com.practice.Client;

import com.practice.Flyweight.Camp;
import com.practice.Flyweight.Soldier;
import com.practice.Flyweight.SoldierFactory;

public class Client {
    public static void main(String [] args) throws Exception {
        SoldierFactory factory = new SoldierFactory();
        
        Soldier fx = factory.getSoldierCategory("法师");
        fx.funcDescription(new Camp("红方阵营"));
        
        Soldier fy = factory.getSoldierCategory("超级兵");
        fy.funcDescription(new Camp("红方阵营"));
        
        Soldier fz = factory.getSoldierCategory("超级兵");
        fz.funcDescription(new Camp("蓝方阵营"));
        
        Soldier fa = factory.getSoldierCategory("超级兵");
        fa.funcDescription(new Camp("红方阵营"));
        
        Soldier fb = factory.getSoldierCategory("法师");
        fb.funcDescription(new Camp("蓝方阵营"));
        
        Soldier fc = factory.getSoldierCategory("法师");
        fc.funcDescription(new Camp("蓝方阵营"));
        
        System.out.println("小兵分类总数:" + factory.getSoldierType());
        System.out.println("小兵总数为:" + factory.getSoldierCount());   
    }
}

运行结果:

结果分析:在Client中,在调用fx等享元对象的funcDescription()方法时,传入了一个Camp类型的对象,在该Camp对象中封装类所属阵营,作为小兵的外部状态

七.源代码下载

从王者荣耀看设计模式(享元模式)

猜你喜欢

转载自www.cnblogs.com/miaowulj/p/12171118.html