享元模式(Flyweight Pattern)

554 阅读3分钟

Motivation(动机)

所谓动机就是为什么我们要使用享元模式。我们先来看看一个场景,在一个战争游戏中,我们需要创建很多个士兵对象。每个士兵对象都有一些状态,外貌、行为(移动、开火),在战场上的健康状态和在战场上的位置。

如果我们为每一个士兵都创建一个对象,并每个对象存储各自的状态。可想而知,这对内存的消耗是很大的。为此,我们需要一种方法来减少对象的创建,于是享元模式就应运而生了。

Intent(意图)

运用共享技术有效地支持大量细粒度的对象,这些对象的内部状态是相同的,而其他状态可以是不同的。

Roles(角色)

享元模式类图如下:

  • Flyweight: 声明一个可以接收并作用外部状态的接口
  • ConcreteFlyweight: 实现Flyweight并存储内部状态。一个ConcreteFlyweight对象必须是可以共享的,它能够维持内部状态并且能够操纵外部状态。在战争游戏中,士兵的外貌和行为是内部状态,士兵在战场上的健康状态和位置是外部状态。士兵移动的行为可以操作其他状态(位置)的改变。
  • FlyweightFactory: 创建和管理Flyweight对象的工厂,能够确保Flyweight对象的共享。它维持了一个保存不同Flyweight对象的池。如果客户端请求的Flyweight对象在池中存在,那么就返回该对象。否则,就创建客户端请求的对象,加入池中并返回给客户端。

Example(示例)

这个战争游戏实例化了5个SoldierClient,每个SoldierClient都维持了Soldier的外部状态。但是仅仅只有一个Soldier对象被使用。

Java Code(该示例的Java代码)

Soldier接口代码如下:(Flyweight)

public interface Soldier {
	
    /**
    * 将一个士兵对象从一个位置移动到另一个位置。
    * 注意,士兵的位置是外部状态
    */
    public void moveSoldier(int previousLocationX, int previousLocationY, int newLocationX, int newLocationY);
}

SoldierImp代码如下:(ConcreteFlyweight)

public class SoldierImp implements Soldier {
	
    /**
    * 维持士兵外貌这个内部状态
    */
    private Object soldierGraphicalRepresentation;
    
    /**
    * 这个方法接收士兵位置。
    * 士兵位置是外部状态,SoldierImp本身不维持先前位置和当前位置的引用。
    */
    public void moveSoldier(int previousLocationX, int previousLocationY, int newLocationX, int newLocationY) {
    	// 删除士兵旧位置的描绘
        // 在新位置渲染出士兵对象
    }
    
}

SoldierFactory代码如下:(FlyweightFactory)

public class SoldierFactory {
	
    /**
    * 仅仅只有一个士兵对象的池
    * 如果拥有多个士兵类型,可以使用数组、list,最好是HashMap
    */
    private static Soldier SOLDIER;
    
    /**
    * 获取士兵对象
    */
    public static Soldier getSoldier() {
    	//判断士兵对象是否存在
    	if(SOLDIER==null) {
        	//不存在就创建新的对象
        	SOLDIER = new SoldierImp();
        }
        //返回士兵对象
        return SOLDIER;
    }
}

注意:上面的代码是存在线程问题的,下面不涉及多线程问题,所以这里的代码就这样写了

SoldierClient

public class SoldierClient {
	
    private Soldier soldier = SoldierFactory.getSoldier();
    
    //存储士兵当前位置状态
    private int currentLocationX = 0;
    private int currentLocationY = 0;
    
    public void moveSoldier(int newLocationX, int newLocationY) {
    	soldier.moveSoldier(currentLocationX, currentLocationY, newLocationX, newLocationY);
        currentLocationX = newLocationX;
        currentLocationY = newLocationY;
    }
    
}

WarGame

public class WarGame {
	
    public static void main(String[] args) {
    	//start war
        //draw war terrian
        
        //创建5个士兵
        SoldierClient warSoldiers[] = {
        	new SoldierClient(),
            new SoldierClient(),
            new SoldierClient(),
            new SoldierClient(),
            new SoldierClient()
        }; 
        
        // 移动士兵
		// 获取用户输入移动各个士兵
		warSoldiers[0].moveSoldier(17, 2112);
		warSoldiers[1].moveSoldier(137, 112);
		
		// 注意到这里只有一个SoldierImp提供给5个士兵。
		// SoldierClient只需要维持很少的状态,所以它的大小是很小的
        // SoldierImp的大小可能很大,也可能很小
		// 然而我们可以节省下创建5个士兵外表的内存消耗
    }
    
}

参考资料