`Java`에서 `Flyweight Design Pattern`

오늘은 Flyweight 디자인 패턴을 살펴보겠습니다.

Flyweight 디자인 패턴

GoF에 따르면, flyweight 디자인 패턴의 의도는 다음과 같습니다:

다수의 세밀한 객체를 효율적으로 지원하기 위해 공유를 사용합니다.

Flyweight 디자인 패턴은 구조적 디자인 패턴으로, 퍼사드 패턴, 어댑터 패턴데코레이터 패턴과 같은 패턴입니다. Flyweight 디자인 패턴은 특정 클래스의 많은 객체를 생성해야 할 때 사용됩니다. 모든 객체는 메모리 공간을 소비하므로, 모바일 기기나 임베디드 시스템과 같은 저메모리 장치의 경우 메모리 부하를 줄이기 위해 flyweight 디자인 패턴을 적용할 수 있습니다. flyweight 디자인 패턴을 적용하기 전에 다음 요소를 고려해야 합니다:

  • 응용 프로그램에서 생성할 객체의 수가 매우 많아야 합니다.
  • 객체 생성이 메모리에 부담이 되고 시간이 오래 걸릴 수 있습니다.
  • 객체 속성은 본질적 속성과 외재적 속성으로 나눌 수 있으며, 객체의 외재적 속성은 클라이언트 프로그램에서 정의되어야 합니다.

플라이웨이트 패턴을 적용하기 위해 객체 속성을 본질적외재적 속성으로 나눌 필요가 있습니다. 본질적 속성은 객체를 고유하게 만들고, 외재적 속성은 클라이언트 코드에서 설정되어 다양한 작업을 수행하는 데 사용됩니다. 예를 들어, 원 객체는 색상과 너비와 같은 외재적 속성을 가질 수 있습니다. 플라이웨이트 패턴을 적용하기 위해 공유 객체를 반환하는 플라이웨이트 팩토리를 생성해야 합니다. 예를 들어, 선과 타원으로 구성된 그림을 만들어야 한다고 가정해봅시다. 그래서 우리는 Shape 인터페이스와 그것의 구체적인 구현체인 LineOval이 있을 것입니다. 타원 클래스는 주어진 색상으로 타원을 채울지를 결정하는 본질적 속성을 가지고 있지만, 선은 어떤 본질적 속성도 가지고 있지 않을 것입니다.

플라이웨이트 디자인 패턴 인터페이스와 구체적인 클래스

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 Enum을 사용하여 형식 안전성을 보장하고, Java Composition (shapes 맵) 및 팩토리 패턴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() 메서드는 플라이웨이트 디자인 패턴의 사용을 보여주기 위해 캐시된 객체를 사용합니다. 가장 좋은 예는 자바 String 클래스의 문자열 풀 구현입니다.

플라이웨이트 디자인 패턴 중요 포인트

  1. 우리의 예제에서, 클라이언트 코드는 플라이웨이트 팩토리를 사용하여 객체를 생성하도록 강제되지 않지만, 특정 응용 프로그램에 대한 완전한 설계 결정으로서 클라이언트 코드가 플라이웨이트 패턴 구현을 사용하도록 강제할 수 있습니다.
  2. 플라이웨이트 패턴은 복잡성을 도입하며, 공유 객체의 수가 많을 경우에는 메모리와 시간 사이의 트레이드 오프가 발생하므로 요구 사항에 맞게 현명하게 사용해야 합니다.
  3. 플라이웨이트 패턴 구현은 객체의 내재적인 속성의 수가 많을 때 유용하지 않으며, 팩토리 클래스의 구현이 복잡해집니다.

자바에서의 플라이웨이트 디자인 패턴에 대한 설명은 여기까지입니다.

Source:
https://www.digitalocean.com/community/tutorials/flyweight-design-pattern-java