设计模式之享元模式

这次又需要大量对象

围棋

从图片可以知道,小明他们这次要做一个围棋的游戏,其中小明负责的就是围棋棋子的创建,棋子的数量很多,小明第一时间想到了之前游戏的小怪,也是需要大量的对象,所以是不是可以用原型模式的,当然不是,我们今天讲的是享元模式!分析一下为什么不是原型模式,原型模式的重要一点是为了快速的产生对象,而产生的新对象和之前的对象在内存中不是指向同一块内存,因为我们本来就是需要大量的不同对象,可能它们的各种状态都不一样。而这次,我们的棋子,颜色、大小和材质都是一模一样,而唯一不一样的就是它们的位置,所以这次,我们需要的是相同的对象,现在我们就用享元模式来实现这个需求

首先定义一个抽象享元类:

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引用了两个完全不同的对象

坚持原创分享,您的支持将鼓励我不断前行!