在面向对象程序设计过程中,有时会面临要创建大量相同或相似对象实例的问题。创建那么多的对象将会耗费很多的系统资源,它是系统性能提高的一个瓶颈。
例如,围棋和五子棋中的黑白棋子,图像中的坐标点或颜色,局域网中的路由器、交换机和集线器,教室里的桌子和凳子等。这些对象有很多相似的地方,如果能把它们相同的部分提取出来共享,则能节省大量的系统资源,这就是享元模式的产生背景。
享元(Flyweight)模式的定义:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
享元模式的主要优点是:相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。
其主要缺点是:
享元模式的定义提出了两个要求,细粒度和共享对象。因为要求细粒度,所以不可避免地会使对象数量多且性质相近,此时我们就将这些对象的信息分为两个部分:内部状态和外部状态。
比如,连接池中的连接对象,保存在连接对象中的用户名、密码、连接URL等信息,在创建对象的时候就设置好了,不会随环境的改变而改变,这些为内部状态。而当每个连接要被回收利用时,我们需要将它标记为可用状态,这些为外部状态。
享元模式的本质是缓存共享对象,降低内存消耗。
享元模式的主要角色有如下。
图 1 是享元模式的结构图,其中:
享元模式的实现代码如下:
- public class FlyweightPattern {
- public static void main(String[] args) {
- FlyweightFactory factory = new FlyweightFactory();
- Flyweight f01 = factory.getFlyweight("a");
- Flyweight f02 = factory.getFlyweight("a");
- Flyweight f03 = factory.getFlyweight("a");
- Flyweight f11 = factory.getFlyweight("b");
- Flyweight f12 = factory.getFlyweight("b");
- f01.operation(new UnsharedConcreteFlyweight("第1次调用a。"));
- f02.operation(new UnsharedConcreteFlyweight("第2次调用a。"));
- f03.operation(new UnsharedConcreteFlyweight("第3次调用a。"));
- f11.operation(new UnsharedConcreteFlyweight("第1次调用b。"));
- f12.operation(new UnsharedConcreteFlyweight("第2次调用b。"));
- }
- }
- //非享元角色
- class UnsharedConcreteFlyweight {
- private String info;
- UnsharedConcreteFlyweight(String info) {
- this.info = info;
- }
- public String getInfo() {
- return info;
- }
- public void setInfo(String info) {
- this.info = info;
- }
- }
- //抽象享元角色
- interface Flyweight {
- public void operation(UnsharedConcreteFlyweight state);
- }
- //具体享元角色
- class ConcreteFlyweight implements Flyweight {
- private String key;
- ConcreteFlyweight(String key) {
- this.key = key;
- System.out.println("具体享元" + key + "被创建!");
- }
- public void operation(UnsharedConcreteFlyweight outState) {
- System.out.print("具体享元" + key + "被调用,");
- System.out.println("非享元信息是:" + outState.getInfo());
- }
- }
- //享元工厂角色
- class FlyweightFactory {
- private HashMap<String, Flyweight> flyweights = new HashMap<String, Flyweight>();
- public Flyweight getFlyweight(String key) {
- Flyweight flyweight = (Flyweight) flyweights.get(key);
- if (flyweight != null) {
- System.out.println("具体享元" + key + "已经存在,被成功获取!");
- } else {
- flyweight = new ConcreteFlyweight(key);
- flyweights.put(key, flyweight);
- }
- return flyweight;
- }
- }
程序运行结果如下:
【例1】享元模式在五子棋游戏中的应用。
分析:五子棋同围棋一样,包含多个“黑”或“白”颜色的棋子,所以用享元模式比较好。
本实例中:
图 2 所示是其结构图。
程序代码如下:
- import javax.swing.*;
- import java.awt.*;
- import java.awt.event.MouseAdapter;
- import java.awt.event.MouseEvent;
- import java.util.ArrayList;
- public class WzqGame {
- public static void main(String[] args) {
- new Chessboard();
- }
- }
- //棋盘
- class Chessboard extends MouseAdapter {
- WeiqiFactory wf;
- JFrame f;
- Graphics g;
- JRadioButton wz;
- JRadioButton bz;
- private final int x = 50;
- private final int y = 50;
- private final int w = 40; //小方格宽度和高度
- private final int rw = 400; //棋盘宽度和高度
- Chessboard() {
- wf = new WeiqiFactory();
- f = new JFrame("享元模式在五子棋游戏中的应用");
- f.setBounds(100, 100, 500, 550);
- f.setVisible(true);
- f.setResizable(false);
- f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- JPanel SouthJP = new JPanel();
- f.add("South", SouthJP);
- wz = new JRadioButton("白子");
- bz = new JRadioButton("黑子", true);
- ButtonGroup group = new ButtonGroup();
- group.add(wz);
- group.add(bz);
- SouthJP.add(wz);
- SouthJP.add(bz);
- JPanel CenterJP = new JPanel();
- CenterJP.setLayout(null);
- CenterJP.setSize(500, 500);
- CenterJP.addMouseListener(this);
- f.add("Center", CenterJP);
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- g = CenterJP.getGraphics();
- g.setColor(Color.BLUE);
- g.drawRect(x, y, rw, rw);
- for (int i = 1; i < 10; i++) {
- //绘制第i条竖直线
- g.drawLine(x + (i * w), y, x + (i * w), y + rw);
- //绘制第i条水平线
- g.drawLine(x, y + (i * w), x + rw, y + (i * w));
- }
- }
- public void mouseClicked(MouseEvent e) {
- Point pt = new Point(e.getX() - 15, e.getY() - 15);
- if (wz.isSelected()) {
- ChessPieces c1 = wf.getChessPieces("w");
- c1.DownPieces(g, pt);
- } else if (bz.isSelected()) {
- ChessPieces c2 = wf.getChessPieces("b");
- c2.DownPieces(g, pt);
- }
- }
- }
- //抽象享元角色:棋子
- interface ChessPieces {
- public void DownPieces(Graphics g, Point pt); //下子
- }
- //具体享元角色:白子
- class WhitePieces implements ChessPieces {
- public void DownPieces(Graphics g, Point pt) {
- g.setColor(Color.WHITE);
- g.fillOval(pt.x, pt.y, 30, 30);
- }
- }
- //具体享元角色:黑子
- class BlackPieces implements ChessPieces {
- public void DownPieces(Graphics g, Point pt) {
- g.setColor(Color.BLACK);
- g.fillOval(pt.x, pt.y, 30, 30);
- }
- }
- //享元工厂角色
- class WeiqiFactory {
- private ArrayList<ChessPieces> qz;
- public WeiqiFactory() {
- qz = new ArrayList<ChessPieces>();
- ChessPieces w = new WhitePieces();
- qz.add(w);
- ChessPieces b = new BlackPieces();
- qz.add(b);
- }
- public ChessPieces getChessPieces(String type) {
- if (type.equalsIgnoreCase("w")) {
- return (ChessPieces) qz.get(0);
- } else if (type.equalsIgnoreCase("b")) {
- return (ChessPieces) qz.get(1);
- } else {
- return null;
- }
- }
- }
程序运行结果如图 3 所示。
当系统中多处需要同一组信息时,可以把这些信息封装到一个对象中,然后对该对象进行缓存,这样,一个对象就可以提供给多出需要使用的地方,避免大量同一对象的多次创建,降低大量内存空间的消耗。
享元模式其实是工厂方法模式的一个改进机制,享元模式同样要求创建一个或一组对象,并且就是通过工厂方法模式生成对象的,只不过享元模式为工厂方法模式增加了缓存这一功能。
前面分析了享元模式的结构与特点,下面分析它适用的应用场景。享元模式是通过减少内存中对象的数量来节省内存空间的,所以以下几种情形适合采用享元模式。
在前面介绍的享元模式中,其结构图通常包含可以共享的部分和不可以共享的部分。在实际使用过程中,有时候会稍加改变,即存在两种特殊的享元模式:单纯享元模式和复合享元模式,下面分别对它们进行简单介绍。
(1) 单纯享元模式,这种享元模式中的所有的具体享元类都是可以共享的,不存在非共享的具体享元类,其结构图如图 4 所示。
(2) 复合享元模式,这种享元模式中的有些享元对象是由一些单纯享元对象组合而成的,它们就是复合享元对象。虽然复合享元对象本身不能共享,但它们可以分解成单纯享元对象再被共享,其结构图如图 5 所示。