深入理解Java享元设计模式:原理、实现与性能优化应用

这是文章《Java 中的享元设计模式》的第1部分(共1部分)。

内容片段: 今天我们将研究享元设计模式。

享元设计模式

根据《设计模式》所述,享元设计模式的意图是:

使用共享来高效地支持大量的细粒度对象。

享元设计模式是一种结构设计模式,就像门面模式、适配器模式和装饰器模式一样。享元设计模式用于在需要创建大量类的对象时使用。由于每个对象消耗的内存空间对于低内存设备(如移动设备或嵌入式系统)可能很关键,因此可以应用享元设计模式来通过共享对象减少内存负载。在应用享元设计模式之前,我们需要考虑以下因素:

  • 应用程序需要创建的对象数量应该非常庞大。
  • 对象创建对内存消耗较大,同时也可能耗时。
  • 对象属性可以分为内在属性和外在属性,对象的外在属性应该由客户端程序定义。

应用享元模式时,我们需要将对象属性分为内在属性和外在属性。内在属性使得对象独一无二,而外在属性由客户端代码设置,并用于执行不同的操作。例如,一个圆形对象可以具有外在属性,如颜色和宽度。为了应用享元模式,我们需要创建一个享元工厂,它返回共享对象。以我们的例子来说,我们需要创建一个包含线条和椭圆的绘图。因此,我们将拥有一个Shape接口以及它的具体实现Line和Oval。Oval类将具有内在属性,用于确定是否使用给定的颜色来填充椭圆,而Line类将不具有任何内在属性。

享元设计模式接口和具体类

形状.java

package com.Olivia.design.flyweight;

import java.awt.Color;
import java.awt.Graphics;

public interface Shape {

	public void draw(Graphics g, int x, int y, int width, int height,
			Color color);
}

线.java

package com.Olivia.design.flyweight;

import java.awt.Color;
import java.awt.Graphics;

public class Line implements Shape {

	public Line(){
		System.out.println("创建线条对象");
		//添加时间延迟
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	@Override
	public void draw(Graphics line, int x1, int y1, int x2, int y2,
			Color color) {
		line.setColor(color);
		line.drawLine(x1, y1, x2, y2);
	}

}

椭圆.java

package com.Olivia.design.flyweight;

import java.awt.Color;
import java.awt.Graphics;

public class Oval implements Shape {
	
	//内在属性
	private boolean fill;
	
	public Oval(boolean f){
		this.fill=f;
		System.out.println("创建椭圆对象,填充属性为="+f);
		//添加时间延迟
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	@Override
	public void draw(Graphics circle, int x, int y, int width, int height,
			Color color) {
		circle.setColor(color);
		circle.drawOval(x, y, width, height);
		if(fill){
			circle.fillOval(x, y, width, height);
		}
	}

}

请注意,我故意在创建具体类的对象时引入了延迟,以此来说明享元模式可用于实例化时间较长的对象。

享元工厂

客户端程序将使用享元工厂来实例化对象,因此我们需要在工厂中保持一个对象的映射,该映射不应该被客户端应用程序访问。每当客户端程序调用获取对象实例的方法时,应该从HashMap中返回对象,如果找不到则创建一个新的对象并放入映射中,然后返回。我们需要确保在创建对象时考虑所有内在属性。我们的享元工厂类的代码如下:ShapeFactory.java

package com.Olivia.design.flyweight;

import java.util.HashMap;

public class ShapeFactory {

	private static final HashMap<ShapeType,Shape> shapes = new HashMap<ShapeType,Shape>();

	public static Shape getShape(ShapeType type) {
		Shape shapeImpl = shapes.get(type);

		if (shapeImpl == null) {
			if (type.equals(ShapeType.OVAL_FILL)) {
				shapeImpl = new Oval(true);
			} else if (type.equals(ShapeType.OVAL_NOFILL)) {
				shapeImpl = new Oval(false);
			} else if (type.equals(ShapeType.LINE)) {
				shapeImpl = new Line();
			}
			shapes.put(type, shapeImpl);
		}
		return shapeImpl;
	}
	
	public static enum ShapeType{
		OVAL_FILL,OVAL_NOFILL,LINE;
	}
}

请注意在getShape方法中,使用Java枚举类型来确保类型安全,使用Java组合(shapes map)和工厂模式。

享元设计模式客户端示例

以下是一个使用享元模式实现的示例程序,DrawingClient.java。

package com.Olivia.design.flyweight;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

import com.Olivia.design.flyweight.ShapeFactory.ShapeType;

public class DrawingClient extends JFrame{

	private static final long serialVersionUID = -1350200437285282550L;
	private final int WIDTH;
	private final int HEIGHT;

	private static final ShapeType shapes[] = { ShapeType.LINE, ShapeType.OVAL_FILL,ShapeType.OVAL_NOFILL };
	private static final Color colors[] = { Color.RED, Color.GREEN, Color.YELLOW };
	
	public DrawingClient(int width, int height){
		this.WIDTH=width;
		this.HEIGHT=height;
		Container contentPane = getContentPane();

		JButton startButton = new JButton("绘制");
		final JPanel panel = new JPanel();

		contentPane.add(panel, BorderLayout.CENTER);
		contentPane.add(startButton, BorderLayout.SOUTH);
		setSize(WIDTH, HEIGHT);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setVisible(true);

		startButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent event) {
				Graphics g = panel.getGraphics();
				for (int i = 0; i < 20; ++i) {
					Shape shape = ShapeFactory.getShape(getRandomShape());
					shape.draw(g, getRandomX(), getRandomY(), getRandomWidth(),
							getRandomHeight(), getRandomColor());
				}
			}
		});
	}
	
	private ShapeType getRandomShape() {
		return shapes[(int) (Math.random() * shapes.length)];
	}

	private int getRandomX() {
		return (int) (Math.random() * WIDTH);
	}

	private int getRandomY() {
		return (int) (Math.random() * HEIGHT);
	}

	private int getRandomWidth() {
		return (int) (Math.random() * (WIDTH / 10));
	}

	private int getRandomHeight() {
		return (int) (Math.random() * (HEIGHT / 10));
	}

	private Color getRandomColor() {
		return colors[(int) (Math.random() * colors.length)];
	}

	public static void main(String[] args) {
		DrawingClient drawing = new DrawingClient(500,600);
	}
}

我使用随机数生成来生成不同类型的形状在我们的框架上。如果你运行上面的客户端程序,你会注意到创建第一个线对象和填充为true和false的椭圆对象的延迟。之后程序执行非常快,因为它使用了共享的对象。点击”绘制”按钮多次后,框架的样子如下图所示。你会在命令行中看到以下输出,确认对象是共享的。

创建线条对象
创建椭圆对象,填充属性为=true
创建椭圆对象,填充属性为=false

关于享元模式的讲解就到这里了,我们将在未来的帖子中继续介绍更多的设计模式。如果你喜欢的话,请在评论区分享你的想法,并与他人分享。

Java开发环境中的享元设计模式示例

所有的包装类的valueOf()方法都使用了缓存对象,展现了享元设计模式的使用。最好的例子是Java字符串类的字符串池实现。

享元设计模式的重要要点

  1. 在我们的例子中,客户端代码不被强制使用享元工厂来创建对象,但我们可以强制它以确保客户端代码使用享元模式的实现,不过这是一种特定应用的完整设计决策。
  2. 享元模式引入了复杂性,如果共享对象的数量很大,那么内存和时间之间存在着一个权衡,所以我们需要根据需求来谨慎使用它。
  3. 当对象的内在属性数量巨大时,实现工厂类会变得复杂,因此享元模式的实现是不适用的。

这就是关于Java中享元设计模式的全部内容。

bannerAds