Padrão de Design Flyweight em Java

Hoje vamos explorar o padrão de design Flyweight.

Padrão de Design Flyweight

De acordo com o GoF, a intenção do padrão de design flyweight é:

Utilizar o compartilhamento para suportar eficientemente um grande número de objetos finamente granulados

O padrão de design Flyweight é um padrão de design estrutural assim como o padrão de fachada, o padrão de adaptador e o padrão decorador. O padrão de design Flyweight é utilizado quando precisamos criar muitos objetos de uma classe. Uma vez que cada objeto consome espaço de memória que pode ser crucial para dispositivos com pouca memória, como dispositivos móveis ou sistemas embarcados, o padrão de design Flyweight pode ser aplicado para reduzir a carga na memória através do compartilhamento de objetos. Antes de aplicarmos o padrão de design Flyweight, precisamos considerar os seguintes fatores:

  • O número de objetos a serem criados pela aplicação deve ser enorme.
  • A criação de objetos consome muita memória e pode ser demorada também.
  • As propriedades do objeto podem ser divididas em propriedades intrínsecas e extrínsecas, sendo que as propriedades extrínsecas de um objeto devem ser definidas pelo programa cliente.

Para aplicar o padrão de peso leve (flyweight pattern), precisamos dividir as propriedades do objeto em propriedades intrínsecas e extrínsecas. Propriedades intrínsecas tornam o objeto único, enquanto propriedades extrínsecas são definidas pelo código do cliente e utilizadas para realizar diferentes operações. Por exemplo, um objeto “Círculo” pode ter propriedades extrínsecas como cor e largura. Para aplicar o padrão de peso leve, precisamos criar uma Fábrica de Peso Leve que retorna objetos compartilhados. No nosso exemplo, digamos que precisamos criar um desenho com linhas e ovais. Assim, teremos uma interface Shape e suas implementações concretas como Line e Oval. A classe Oval terá uma propriedade intrínseca para determinar se preenche o oval com uma determinada cor, enquanto a Line não terá nenhuma propriedade intrínseca.

Interface e Classes Concretas do Padrão de Peso Leve

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");
		//adicionando atraso de tempo
		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 {
	
	//propriedade intrínseca
	private boolean fill;
	
	public Oval(boolean f){
		this.fill=f;
		System.out.println("Creating Oval object with fill="+f);
		//adicionando atraso de tempo
		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);
		}
	}

}

Repare que intencionalmente introduzi um atraso na criação do objeto das classes concretas para destacar que o padrão de flyweight pode ser usado para objetos que levam muito tempo durante a instanciação.

Fábrica de Flyweight

A fábrica de flyweight será usada pelos programas clientes para instanciar o objeto, então precisamos manter um mapa de objetos na fábrica que não deve ser acessível pelo aplicativo cliente. Sempre que o programa cliente fizer uma chamada para obter uma instância do objeto, ela deve ser retornada do HashMap; se não for encontrada, então criar um novo objeto e colocá-lo no mapa e depois retorná-lo. Precisamos garantir que todas as propriedades intrínsecas sejam consideradas durante a criação do objeto. Nossa classe de fábrica de flyweight parece com o código abaixo. 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;
	}
}

Observe o uso de Enum Java para segurança de tipo, Composição Java (mapa de formas) e Padrão de Fábrica no método getShape.

Exemplo de Cliente de Padrão de Projeto Flyweight

Abaixo está um programa de exemplo que consome a implementação do padrão flyweight. 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

Isso é tudo para o padrão flyweight, vamos examinar mais padrões de design em futuras postagens. Se você gostou, compartilhe seus pensamentos na seção de comentários e compartilhe também com outros.

Exemplo de Padrão de Projeto Flyweight no JDK

Todas as classes de invólucro método valueOf() usam objetos em cache, mostrando o uso do padrão de design Flyweight. O melhor exemplo é a implementação da classe Java String String Pool.

Pontos Importantes do Padrão de Design Flyweight

  1. No nosso exemplo, o código do cliente não é obrigado a criar um objeto usando a fábrica Flyweight, mas podemos forçar isso para garantir que o código do cliente utilize a implementação do padrão flyweight, embora seja uma decisão de design completa para uma aplicação específica.
  2. O padrão flyweight introduz complexidade e, se o número de objetos compartilhados for grande, há um trade-off entre memória e tempo, então precisamos usá-lo com discernimento com base em nossos requisitos.
  3. A implementação do padrão flyweight não é útil quando o número de propriedades intrínsecas do objeto é grande, tornando a implementação da classe Factory complexa.

Isso é tudo para o padrão de design Flyweight em Java.

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