JavaにおけるFlyweightデザインパターン
今日は、フライウェイトデザインパターンについて調べてみましょう。
フライウェイトデザインパターン
GoFによると、フライウェイトデザインパターンの目的は次のとおりです。
多くの細粒度なオブジェクトを効率的にサポートするために共有を利用してください。
フライウェイトデザインパターンは、ファサードパターン、アダプターパターン、およびデコレーターパターンのような構造デザインパターンです。フライウェイトデザインパターンは、クラスの多くのオブジェクトを作成する必要がある場合に使用されます。オブジェクトごとにメモリスペースを消費するため、メモリの負荷を減らすためにフライウェイトデザインパターンを適用することができます。これは、モバイルデバイスや組み込みシステムなどのメモリが制約されているデバイスにとって重要です。フライウェイトデザインパターンを適用する前に、以下の要素を考慮する必要があります。
- The number of Objects to be created by application should be huge.
- The object creation is heavy on memory and it can be time consuming too.
- The object properties can be divided into intrinsic and extrinsic properties, extrinsic properties of an Object should be defined by the client program.
フライウェイトパターンを適用するためには、オブジェクトのプロパティを内在的なものと外在的なものに分ける必要があります。内在的なプロパティはオブジェクトをユニークにし、外在的なプロパティはクライアントコードによって設定され、さまざまな操作に使用されます。たとえば、円のオブジェクトには色や幅などの外在的なプロパティがあります。フライウェイトパターンを適用するために、共有オブジェクトを返すフライウェイトファクトリを作成する必要があります。例えば、線と楕円で描画を作成する必要があるとします。そのために、Shapeというインターフェースと、その具象クラスであるLineとOvalを用意します。楕円のクラスには内在的なプロパティがあり、与えられた色で楕円を塗りつぶすかどうかを決定しますが、線には内在的なプロパティはありません。
フライウェイトのデザインパターンのインターフェースと具象クラス
Shape.javaを日本語で言い換えると、「形.java」です。
package com.scdev.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 を日本語で言い換えると:「Lineクラス」
package com.scdev.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);
}
}
オーバル.java
package com.scdev.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);
}
}
}
意図的に遅延を導入して、具体的なクラスのオブジェクトの作成に時間がかかる場合でも、フライウェイトパターンが使用できることを明示してください。
フライウェイトファクトリー
クライアントプログラムはオブジェクトをインスタンス化するためにフライウェイト工場を使用するため、クライアントアプリケーションからアクセスできないオブジェクトのマップを工場に保持する必要があります。クライアントプログラムがオブジェクトのインスタンスを取得するために呼び出しを行うと、HashMapから返されるべきです。もし見つからなければ、新しいオブジェクトを作成し、マップに入れてからそれを返す必要があります。オブジェクトを作成する際には、すべての本質的なプロパティを考慮する必要があります。フライウェイト工場クラスは以下のコードのようになります。ShapeFactory.java
package com.scdev.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(shapes map)とFactoryパターンがgetShapeメソッドで使われていることに注目してください。
フライウェイトデザインパターンのクライアントの例
以下には、フライウェイトパターンの実装を使用するサンプルプログラムがあります。DrawingClient.java
package com.scdev.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.scdev.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);
}
}
フレーム内でさまざまな形の生成を行うために、ランダムな数値生成を使用しました。上記のクライアントプログラムを実行すると、最初のLine ObjectおよびfillがtrueおよびfalseのOvalオブジェクトの作成に遅延があることがわかります。その後、プログラムは共有オブジェクトを使用しているため、速く実行されます。複数回「Draw」ボタンをクリックすると、フレームは以下の画像のようになります。また、コマンドライン上で以下の出力が表示され、オブジェクトが共有されていることが確認できます。
Creating Line object
Creating Oval object with fill=true
Creating Oval object with fill=false
フライウェイトパターンに関しては以上です。今後の記事では、他のデザインパターンについても紹介します。もし気に入っていただけたら、コメント欄にご意見をお寄せいただき、他の方々とも共有してください。
JDK内でのFlyweightデザインパターンの例
すべてのラッパークラスのvalueOf()メソッドは、Flyweightデザインパターンの使用を示すキャッシュオブジェクトを使用します。最良の例は、JavaのStringクラスのString Pool実装です。
フライウェイトデザインパターンの重要なポイント
-
- 以下は日本語での一つのオプションです:
例えば、クライアントコードはFlyweightファクトリを使用してオブジェクトを作成することは強制されませんが、Flyweightパターンの実装を使用するようにクライアントコードに強制することもできます。ただし、これは特定のアプリケーションの完全なデザインの決定です。
Flyweightパターンは複雑さを導入するため、共有オブジェクトの数が多い場合はメモリと時間のトレードオフが発生します。そのため、要件に応じて賢明に使用する必要があります。
Flyweightパターンの実装は、オブジェクトの固有のプロパティの数が多い場合には、ファクトリクラスの実装が複雑になるため、役に立ちません。
JavaにおけるFlyweightデザインパターンについては以上です。