我们用Java 编程时经常听说 “从对象的角度思考” 和 “一切皆是对象” 这样的金句,因为在开发应用时对象设计是最小颗粒度的设计,好的设计可以提升系统的灵活性。但是,作为一个好的程序员也需要根据对象所消耗的资源总量来考虑应用的性能,当你处理大量的对象时,你需要关注对象将要消耗的资源,包括内存的占用和使用CPU的时间。
假如在一个赛程游戏里你需要1000个汽车对象,这些对象唯一的不同点是在赛道上的某时刻的位置信息,为了避免创建1000个汽车对象或者当更多用户加入游戏时创建更多的对象,你可以创建包含共有信息的汽车对象单例和一个包含汽车位置状态的客户端对象。享元模式减少重复的数据,因此在创建大量对象时可以减少内存的占用。
享元模式是一个结构型的设计模式,利用共享对象的概念实现,开发应用时,如果需要创建大量的细粒度的对象,这时应该采用共享的方式而不是重复创建,被共享的对象我们称为 flyweight
。
为了更好的理解享元模式,我们需要理解两个概念:
继续拿赛车游戏来说,我们需要两种赛车:Midget 和 Sprint , 首先创建一个基准抽象类 RaceCar
, 包含3个字段:
name
汽车的名称,String 类型speed
汽车最大速度,int 类型horsepower
马力,int 类型子类,如FlyweightMidgetCar
对三个字段存的值为:
name="Midget Car";
speed=140;
horsepower=400;
这些字段是 FlyweightMidgetCar 的固有数据,被不同的对象共享,不会被其他共享的对象所改变。
在基类中,定义了一个抽象方法 moveCar(int currentX, int currentY, int newX ,int newY)
,子类重写此方法,汽车的位置在不同的对象之间是不同的,我们成为非固有数据。
为了管理享元对象,我们需要一个工厂类CarFactory
,此类需要一个池子来存储享元对象,当客户端第一次获取对象时用固有数据初始化对象,初始化后放入池子然后返回给客户端。
我们还需一个客户端类RaceCarClient
,此类会从 CarFactory 获取享元对象,并把非固有数据传给享元对象,在此类中,将把汽车的当前位置传给方法 moveCar(int currentX, int currentY, int newX ,int newY)
。
现在我们把此例子中的角色按照享元模式的参与者进行归类:
public abstract class RaceCar {
/*Intrinsic state stored and shared in the Flyweight object*/
String name;
int speed;
int horsePower;
/* Extrinsic state is stored or computed by client objects, and passed to the Flyweight.*/
abstract void moveCar(int currentX, int currentY, int newX ,int newY);
}
public class FlyweightSprintCar extends RaceCar{
/*Track number of flyweight instantiation*/
public static int num;
public FlyweightSprintCar()
{
num++;
}
/*This method accepts car location (extrinsic). No reference to current
*or new location is maintained inside the flyweight implementation*/
@Override
public void moveCar(int currentX, int currentY, int newX ,int newY)
{
System.out.println("New location of "+this.name+" is X"+newX + " - Y"+newY);
}
}
public class FlyweightMidgetCar extends RaceCar {
/*Track number of flyweight instantiation*/
public static int num;
public FlyweightMidgetCar()
{
num++;
}
/*This method accepts car location (extrinsic). No reference to current
*or new location is maintained inside the flyweight implementation*/
@Override
public void moveCar(int currentX, int currentY, int newX ,int newY)
{
System.out.println("New location of "+this.name+" is X"+newX + " - Y"+newY);
}
}
public class CarFactory {
private static Map<String, RaceCar> flyweights = new HashMap<>();
/*If key exist, return flyweight from Map*/
public static RaceCar getRaceCar(String key) {
if(flyweights.containsKey(key)){
return flyweights.get(key);
}
RaceCar raceCar;
/*If key does not exist in Map, create flyweight, put it in Map, and return the object*/
switch (key)
{
case "Midget":
raceCar = new FlyweightMidgetCar();
raceCar.name="Midget Car";
raceCar.speed=140;
raceCar.horsePower=400;
break;
case "Sprint":
raceCar = new FlyweightSprintCar();
raceCar.name="Sprint Car";
raceCar.speed=160;
raceCar.horsePower=1000;
break;
default:
throw new IllegalArgumentException("Unsupported car type.");
}
flyweights.put(key, raceCar);
return raceCar;
}
}
public class RaceCarClient {
private RaceCar raceCar;
public RaceCarClient(String name) {
/*Ask factory for a RaceCar*/
raceCar = CarFactory.getRaceCar(name);
}
/**
* The extrinsic state of the flyweight is maintained by the client
*/
private int currentX = 0;
private int currentY=0;
public void moveCar(int newX, int newY){
/*Car movement is handled by the flyweight object*/
raceCar.moveCar(currentX,
currentY, newX, newY);
currentX = newX;
currentY = newY;
}
}
public class RaceCarClientTest {
@Test
public void testRaceCar() throws Exception {
RaceCarClient raceCars[] = {
new RaceCarClient("Midget"),
new RaceCarClient("Midget"),
new RaceCarClient("Midget"),
new RaceCarClient("Sprint"),
new RaceCarClient("Sprint"),
new RaceCarClient("Sprint")
};
raceCars[0].moveCar(29, 3112);
raceCars[1].moveCar(39, 2002);
raceCars[2].moveCar(49, 1985);
raceCars[3].moveCar(59, 2543);
raceCars[4].moveCar(69, 2322);
raceCars[5].moveCar(79, 2135);
/*Output and observe the number of instances created*/
System.out.println("Midget Car Instances: " + FlyweightMidgetCar.num);
System.out.println("Sprint Car Instances: " + FlyweightSprintCar.num);
}
}
运行单元测试:
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running guru.springframework.gof.flyweight.RaceCarClientTest
New location of Midget Car is X29 - Y3112
New location of Midget Car is X39 - Y2002
New location of Midget Car is X49 - Y1985
New location of Sprint Car is X59 - Y2543
New location of Sprint Car is X69 - Y2322
New location of Sprint Car is X79 - Y2135
Midget Car Instances: 1
Sprint Car Instances: 1
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.01 sec - in guru.springframework.gof.flyweight.RaceCarClientTest
Jdk 中使用享元模式的例子:
Java.lang.Integer#valueOf(int) (also on Boolean, Byte, Character, Short and Long)