📜 ⬆️ ⬇️

Design pattern "Fitter" / "Flyweight"

Read the description of other patterns.

Problem


Designing objects of the lowest levels of the system can provide optimal flexibility, however, is accompanied by unacceptable memory and performance.

Description


As already noted, there are a large number of software systems, the purpose of which is to construct complex composite objects from a large number of smaller and simple objects. At the same time, the flexibility and versatility of such systems is achieved by providing the user with a complete set of tools and primitives. It is important to understand that the primitives in this context are elementary objects from which composite are subsequently constructed. Moreover, the level at which the object is considered primitive, in fact, determines the applicability and effectiveness of this system. However, it is not always possible to design a system down to the lowest levels of abstraction. The cost of memory and low system performance, with the direct approach, do not allow this. Therefore, when designing such systems, the “Fitter” pattern is often used.

Adaptive uses separation to effectively support many small objects.

The main idea of ​​the pattern is the difference between the internal and external state of the object. The external state is transmitted by the client using the opportunist in some context. The internal state is stored directly in the opportunist and allows you to separate them. Under the division refers to the possibility of simultaneous work of several clients with the same opportunist. Thus, there are two types of opportunists - shared and not shared. It is obvious that a client working with adapters does not make assumptions about their nature, but only uses them in a given context. Therefore, the client should be provided with a transparent mechanism for separating adapters.
')
Adaptive can be simultaneously used in different contexts. He seems to be "adapting" to the context of his use. At the client, at the same time, it seems that he works with different objects. Although, in fact, it is one and the same opportunist.

Summing up, all of the above, it is worth noting that opportunists model entities of the subject area, the number of which is too large to represent them as real objects.

Practical task


Consider the problem of designing a vector editor. Moreover, for maximum flexibility of the editor, it is necessary to design it down to the lowest levels - to the elementary primitive - point. Obviously, with the direct approach in one drawing there can be a huge number of points, working with which and storing them in the memory of the editor will not be easy. Therefore, we will design the editor using the “Fitter” pattern.

Let the vector editor know how to work with a limited set of primitives - Square (Square), Circle (Circle), Point (Point) and Picture (Picture). All these primitives are adapters. Moreover, only the image is not a divided adaptive, due to its composite nature. It is worth noting that the image in this case is an example of the “Linker” pattern. The rest of the primitives should be divided on the basis of their internal state. For a square - the size of the sides (height, width), for a circle - the radius (radius). The point, although it is a shared opportunist, does not have an internal state, in view of the fact that all the information necessary for its rendering is given by the client to the so-called. context of drawing (Context).

Class diagram


In this example, a factory, PrimitiveFactory, was used to implement the mechanism for adapting the opportunists. The factory is not the only mechanism for separating objects. In some cases, you can get by with static class members.


Java implementation


/*
*
*/
public interface Primitive {
/*
*
*/
public void draw(Context context);
}

/*
* - . -
*/
public class Circle implements Primitive {
private int radius;

public Circle( int radius) {
this .radius = radius;
}

@Override
public void draw(Context context) { }
}

/*
* - .
* - , .
*/
public class Square implements Primitive {
private int height, width;

public Square( int height, int width) {
this .height = height;
this .width = width;
}

@Override
public void draw(Context context) { }
}

/*
* -
*/
public class Point implements Primitive {
@Override
public void draw(Context context) { }
}

import java.util.Arrays;
import java.util.LinkedList;
import java.util. List ;

/*
* - .
*/
public class Picture implements Primitive {
private List <Primitive> childrens;

public Picture(Primitive ...primitives) {
this .childrens = new LinkedList<Primitive>();
this .childrens.addAll(Arrays.asList(primitives));
}

@Override
public void draw(Context context) {
for (Primitive p: childrens) {
p.draw(context);
}
}
}

import java.awt.Color;
/*
* ,
*/
public final class Context {
public final int x;
public final int y;
public final Color color;

public Context( int x, int y, Color collor) {
this .x = x;
this .y = y;
this .color = collor;
}
}

import java.util.HashMap;
import java.util.Map;

/*
* .
* .
*
*/
public abstract class PrimitiveFactory {

private static Point onePoint;
private static Map<Integer, Circle> circles;
private static Map<Integer, Square> squares;

static {
circles = new HashMap<Integer, Circle>();
squares = new HashMap<Integer, Square>();
}

public static synchronized Picture createPicture(Primitive ... childrens) {
return new Picture(childrens);
}

public static synchronized Circle createCircle( int radius) {
if (circles. get (radius) == null ) {
circles.put(radius, new Circle(radius));
}

return circles. get (radius);
}

public static synchronized Square createSquare( int height, int width) {
if (squares. get (height*10+width) == null ) {
squares.put(height*10+width, new Square(height, width));
}

return squares. get (height*10+width);
}

public static synchronized Point createPoint() {
if (onePoint == null ) {
onePoint = new Point();
}

return onePoint;
}
}

import java.awt.Color;

//
public class Main {
public static void main( String [] args) {

Primitive[] primitives = {
PrimitiveFactory.createPoint(),
PrimitiveFactory.createCircle(10),
PrimitiveFactory.createSquare(20, 30),
PrimitiveFactory.createCircle(20),
PrimitiveFactory.createCircle(20),
PrimitiveFactory.createPoint(),
PrimitiveFactory.createSquare(20, 40),
};

Picture picture = PrimitiveFactory.createPicture(primitives);
Context context = new Context(10, 20, Color.BLUE);
picture.draw(context);
}
}


* This source code was highlighted with Source Code Highlighter .


It is important to understand that the applicability of this pattern is determined, first of all, by how clearly the internal and external states of the system objects are identified.

Source: https://habr.com/ru/post/88393/


All Articles