这次又需要大量对象
从图片可以知道,小明他们这次要做一个围棋的游戏,其中小明负责的就是围棋棋子的创建,棋子的数量很多,小明第一时间想到了之前游戏的小怪,也是需要大量的对象,所以是不是可以用原型模式的,当然不是,我们今天讲的是享元模式!分析一下为什么不是原型模式,原型模式的重要一点是为了快速的产生对象,而产生的新对象和之前的对象在内存中不是指向同一块内存,因为我们本来就是需要大量的不同对象,可能它们的各种状态都不一样。而这次,我们的棋子,颜色、大小和材质都是一模一样,而唯一不一样的就是它们的位置,所以这次,我们需要的是相同的对象,现在我们就用享元模式来实现这个需求
首先定义一个抽象享元类:
public abstract class Piece {
public abstract String getColor();
public void display() {
System.out.println("棋子颜色:" + getColor());
}
}
黑色棋子:
public class BlackPiece extends Piece {
@Override
public String getColor() {
return "黑色";
}
}
白色棋子:
public class WhitePiece extends Piece {
@Override
public String getColor() {
return "白色";
}
}
工厂类:
public class PieceFactory {
private static PieceFactory instance = new PieceFactory();
private HashMap<String, Piece> pieceHashMap = new HashMap<>();
public static PieceFactory getInstance() {
return instance;
}
private PieceFactory() {
pieceHashMap.put("黑色", new BlackPiece());
pieceHashMap.put("白色", new WhitePiece());
}
public Piece getPiece(String color) {
return pieceHashMap.get(color);
}
}
使用棋子:
public class Client {
public static void main(String[] args) {
Piece p1 = PieceFactory.getInstance().getPiece("黑色");
Piece p2 = PieceFactory.getInstance().getPiece("黑色");
Piece p3 = PieceFactory.getInstance().getPiece("白色");
Piece p4 = PieceFactory.getInstance().getPiece("白色");
p1.display();
p2.display();
p3.display();
p4.display();
System.out.println("p1: " + p1);
System.out.println("p2: " + p2);
System.out.println("p3: " + p3);
System.out.println("p4: " + p4);
}
}
结果:
棋子颜色:黑色
棋子颜色:黑色
棋子颜色:白色
棋子颜色:白色
p1: com.jucongyuan.testsdk.BlackPiece@1b6d3586
p2: com.jucongyuan.testsdk.BlackPiece@1b6d3586
p3: com.jucongyuan.testsdk.WhitePiece@4554617c
p4: com.jucongyuan.testsdk.WhitePiece@4554617c
可以看到,相同颜色棋子的内存地址是一样的,这就是我们的享元模式,我们的棋子就是享元类,另外我们还使用了工厂模式
定义
运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用
角色
- 抽象享元类
- 具体享元类
- 非共享具体享元类
- 享元工厂类
非共享具体享元类指的是不被共享的享元类,可以直接使用,没有非共享具体享元类为单纯享元模式,如果有,则为复合享元模式
是否我们的需求还没有完成
我们的对象是共用了,但是有一个问题,虽然说所有白色的对象都是一个,所有黑色的也是一样,但是我们要他们的位置不一样啊!这里就不得不提我们享元模式的内部状态和外部状态了,刚才我们的实现,享元类只有内部状态,内部状态是不变的,也是被大家共享的,比如刚才提到过的棋子的颜色、大小和材质,现在我们引入外部状态
先实现一个外部状态类,来表示坐标
public class Coordinates {
private int x;
private int y;
public Coordinates(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
现在,在享元类中加入外部状态
public abstract class Piece {
public abstract String getColor();
public void display(Coordinates coordinates) {
System.out.println("棋子颜色:" + this.getColor() + ",棋子位置:" + coordinates.getX() + "," + coordinates.getY());
}
}
再使用一下棋子:
public class Client {
public static void main(String[] args) {
Piece p1 = PieceFactory.getInstance().getPiece("黑色");
Piece p2 = PieceFactory.getInstance().getPiece("黑色");
Piece p3 = PieceFactory.getInstance().getPiece("白色");
Piece p4 = PieceFactory.getInstance().getPiece("白色");
Coordinates coordinates1 = new Coordinates(3, 6);
Coordinates coordinates2 = new Coordinates(4, 5);
Coordinates coordinates3 = new Coordinates(1, 5);
Coordinates coordinates4 = new Coordinates(5, 5);
p1.display(coordinates1);
p2.display(coordinates2);
p3.display(coordinates3);
p4.display(coordinates4);
System.out.println("p1: " + p1);
System.out.println("p2: " + p2);
System.out.println("p3: " + p3);
System.out.println("p4: " + p4);
}
}
结果:
棋子颜色:黑色,棋子位置:3,6
棋子颜色:黑色,棋子位置:4,5
棋子颜色:白色,棋子位置:1,5
棋子颜色:白色,棋子位置:5,5
p1: com.jucongyuan.testsdk.BlackPiece@1b6d3586
p2: com.jucongyuan.testsdk.BlackPiece@1b6d3586
p3: com.jucongyuan.testsdk.WhitePiece@4554617c
p4: com.jucongyuan.testsdk.WhitePiece@4554617c
JDK中的享元模式
Java中的String就是享元模式,我们用一个例子来看看
public class Client {
public static void main(String[] args) {
String str1 = "abcd";
String str2 = "abcd";
String str3 = "ab" + "cd";
String str4 = "ab";
str4 += "cd";
System.out.println(str1 == str2);
System.out.println(str1 == str3);
System.out.println(str1 == str4);
str2 += "e";
System.out.println(str1 == str2);
}
}
结果:
true
true
false
false
可以看到,对于字符串abcd
,第一次创建后,再次创建时,对象引用会指向第一次创建的内存位置。如果有一个字符串str4,其初值为”ab”,再对它进行操作str4 += “cd”,此时虽然str4的内容与str1相同,但是由于str4的初始值不同,在创建str4时重新分配了内存,所以第三个输出语句结果为false。最后一个输出语句结果也为false,说明当对str2进行修改时将创建一个新的对象,修改工作在新对象上完成,而原来引用的对象并没有发生任何改变,str1仍然引用原有对象,而str2引用新对象,str1与str2引用了两个完全不同的对象