Java 中的享元设计模式
今天我们将研究享元设计模式。
享元设计模式
按照GoF的说法,享元设计模式的意图是:
使用共享来有效地支持大量细粒度的对象
享元设计模式是一种结构设计模式,类似于外观模式、适配器模式和装饰模式。当我们需要创建大量类的对象时,可以使用享元设计模式。由于每个对象都会占用内存空间,而这对于低内存设备(如移动设备或嵌入式系统)来说至关重要,因此可以应用享元设计模式,通过共享对象来减少内存负载。在应用享元设计模式之前,我们需要考虑以下因素:
- 应用程序要创建的对象数量应该非常庞大。
- 对象的创建会占用大量内存并且也很耗时。
- 对象属性可以分为内在属性和外在属性,对象的外在属性应该由客户端程序定义。
要应用享元模式,我们需要将对象属性分为内在属性和外在属性。内在属性使对象独一无二,而外在属性由客户端代码设置并用于执行不同的操作。例如,对象 Circle 可以具有外在属性,例如颜色和宽度。要应用享元模式,我们需要创建一个返回共享对象的享元工厂。在我们的例子中,假设我们需要创建一个带有线条和椭圆的图形。因此,我们将有一个接口Shape
及其具体实现Line
和Oval
。Oval 类将具有内在属性来确定是否用给定的颜色填充椭圆,而 Line 则没有任何内在属性。
享元设计模式接口和具体类
Shape.java
package com.journaldev.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);
}
Line.java
package com.journaldev.design.flyweight;
import java.awt.Color;
import java.awt.Graphics;
public class Line implements Shape {
public Line(){
System.out.println("Creating Line object");
//adding time delay
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);
}
}
Oval.java
package com.journaldev.design.flyweight;
import java.awt.Color;
import java.awt.Graphics;
public class Oval implements Shape {
//intrinsic property
private boolean fill;
public Oval(boolean f){
this.fill=f;
System.out.println("Creating Oval object with fill="+f);
//adding time delay
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);
}
}
}
请注意,我特意引入了创建具体类的对象时的延迟,以表明享元模式可以用于实例化时需要大量时间的对象。
享元工厂
客户端程序将使用 flyweight 工厂来实例化对象,因此我们需要在工厂中保留一个对象映射,客户端应用程序不应访问该映射。每当客户端程序调用以获取对象实例时,它都应从 HashMap 返回,如果没有找到,则创建一个新对象并将其放入 Map 中,然后返回它。我们需要确保在创建对象时考虑所有内在属性。我们的 flyweight 工厂类如下所示。ShapeFactory.java
package com.journaldev.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;
}
}
注意在方法中使用Java Enum来实现类型安全,使用Java Composition(形状图)和Factory 模式。getShape
享元设计模式客户端示例
下面是一个使用享元模式实现的示例程序。DrawingClient.java
package com.journaldev.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.journaldev.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("Draw");
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);
}
}
我使用随机数生成在我们的框架中生成不同类型的形状。如果您运行上述客户端程序,您会注意到在创建第一个线对象和椭圆对象时出现延迟,其中填充为真和假。之后,由于使用共享对象,程序执行速度很快。多次单击“绘制”按钮后,框架如下图所示。您将在命令行中看到以下输出,确认对象是共享的。
Creating Line object
Creating Oval object with fill=true
Creating Oval object with fill=false
这就是关于享元模式的全部内容,我们将在以后的文章中探讨更多设计模式。如果您喜欢它,请在评论部分分享您的想法并与其他人分享。
JDK 中的享元设计模式示例
所有包装类 valueOf()
方法都使用缓存对象,体现了 Flyweight 设计模式的使用。最好的例子是Java String类的String Pool实现。
享元设计模式重点
- 在我们的例子中,客户端代码没有被强制使用享元工厂来创建对象,但我们可以强制这样做以确保客户端代码使用享元模式实现,但这是一个针对特定应用程序的完整设计决策。
- 享元模式引入了复杂性,并且如果共享对象的数量巨大,那么就需要在内存和时间之间进行权衡,因此我们需要根据我们的需求明智地使用它。
- 当对象的固有属性数量巨大时,享元模式实现并不有用,这会使工厂类的实现变得复杂。
这就是 Java 中的享元设计模式的全部内容。