今天我们将深入研究享元设计模式。
享元设计模式
根据GoF,《diy6>享元设计模式的目的是:
使用共享来有效地支持大量细粒度对象
享元设计模式是一种结构性设计模式,类似于外观模式、适配器模式和装饰器模式。在需要创建大量类的对象时,可以应用享元设计模式。由于每个对象消耗内存空间,这对于低内存设备(如移动设备或嵌入式系统)可能至关重要,享元设计模式可用于通过共享对象来减轻内存负担。在应用享元设计模式之前,我们需要考虑以下因素:
- 应用程序需要创建大量对象。
- 对象的创建对内存消耗较大,而且可能耗时。
- 物体属性可分为内在属性和外在属性,对象的外在属性应由客户程序定义。
为应用享元模式,我们需要将对象属性划分为内在和外在属性。内在属性使对象独特,而外在属性由客户代码设置,并用于执行不同的操作。例如,对象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");
//添加时间延迟
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 {
//内在属性
private boolean fill;
public Oval(boolean f){
this.fill=f;
System.out.println("Creating Oval object with fill="+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.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枚举中使用类型安全,Java组合(形状映射)和工厂模式在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);
}
}
I have used random number generation to generate different type of Shapes in our frame. If you run above client program, you will notice the delay in creating first Line Object and Oval objects with fill as true and false. After that the program executes quickly since its using the shared objects. After clicking “Draw” button multiple times, the frame looks like below image. And you will see following output in command line confirming that Objects are shared.
Creating Line object
Creating Oval object with fill=true
Creating Oval object with fill=false
关于享元模式的内容就是这些,我们将在未来的文章中探讨更多设计模式。如果你喜欢,请在评论区分享你的想法,也请与他人分享。
JDK中的享元设计模式示例
所有包装类的valueOf()
方法都使用了缓存对象,展示了享元设计模式的使用。最好的例子就是Java String类的字符串池实现。
享元设计模式要点
- 在我们的例子中,客户端代码并非被强制使用享元工厂创建对象,但我们可以强制这样做,以确保客户端代码使用享元模式的实现,但这是特定应用的完全设计决策。
- 享元模式引入了复杂性,如果共享对象的数量很大,那么在内存和时间之间存在权衡,因此我们需要根据我们的需求明智地使用它。
- 当对象的内在属性数量巨大时,享元模式的实现就不再有用,这会使工厂类的实现变得复杂。
关于Java中的享元设计模式就介绍到这里。
Source:
https://www.digitalocean.com/community/tutorials/flyweight-design-pattern-java