Шаблон проектирования Flyweight в Java

Сегодня мы рассмотрим шаблон проектирования Приспособленец.

Шаблон проектирования Приспособленец

Согласно GoF, намерение шаблона проектирования приспособленца заключается в следующем:

Использовать совместное использование для эффективной поддержки большого количества мелкозернистых объектов

Шаблон проектирования Приспособленец является структурным шаблоном проектирования подобно шаблону Фасад, шаблону Адаптер и шаблону Декоратор. Шаблон проектирования Приспособленец используется, когда нам нужно создать множество объектов класса. Поскольку каждый объект занимает пространство в памяти, что может быть критично для устройств с низким объемом памяти, таких как мобильные устройства или встроенные системы, шаблон проектирования Приспособленец может быть применен для снижения нагрузки на память за счет совместного использования объектов. Прежде чем применять шаблон проектирования Приспособленец, необходимо учитывать следующие факторы:

  • Количество объектов, которые должно создать приложение, должно быть огромным.
  • Создание объекта требует много памяти и может быть времязатратным.
  • Свойства объекта можно разделить на внутренние и внешние. Внешние свойства объекта должны быть определены клиентской программой.

Для применения шаблона “легковес”, нам нужно разделить свойства объекта на внутренние и внешние. Внутренние свойства делают объект уникальным, тогда как внешние свойства устанавливаются клиентским кодом и используются для выполнения различных операций. Например, у объекта “Круг” могут быть внешние свойства, такие как цвет и ширина. Для применения шаблона “легковес” нам нужно создать фабрику легковесов, возвращающую общие объекты. В нашем примере давайте сказать, что нам нужно создать рисунок с линиями и овалами. Так что у нас будет интерфейс 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 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);
	}
}

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 с реализацией пула строк.

Важные моменты шаблона проектирования “Легковес”

  1. В нашем примере клиентский код не обязан создавать объект с использованием фабрики “Легковес”, но мы можем заставить его это делать, чтобы убедиться, что клиентский код использует реализацию шаблона “Легковес”, но это полностью решение по дизайну для конкретного приложения.
  2. Шаблон “Легковес” вводит сложность, и если количество общих объектов огромно, то есть компромисс между памятью и временем, поэтому мы должны использовать его осмотрительно в зависимости от наших требований.
  3. Реализация шаблона “Легковес” не является полезной, когда количество внутренних свойств объекта огромно, что делает реализацию класса фабрики сложной.

Это все, что касается шаблона проектирования “Легковес” в Java.

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