📜 ⬆️ ⬇️

Design pattern "Linker" / "Composite"

Read the description of other patterns.

Problem


Provide the client with uniform access to the leaf and constituent elements of the tree structure.

Description


There is a large number of software systems in which tree structures of objects are used in one way or another. In most cases, this is all kinds of designers / editors that allow you to collect something large (composite) from something smaller (sheet) . In this case, the client treats both large and small as the same thing, and the system must distinguish between composite and leaf objects, respectively.

Not only constructors use this pattern (and I hope they use it). A striking example of such a tree structure is the user interface (GUI). Indeed, a typical user interface window is a container for simpler widgets — panels, buttons, water fields, etc., moreover, the panel, in turn, are also container objects and so on down to elementary sheet objects. A vivid example of using this pattern in this context is the Swing interface rendering library (I mean the javax.swing.JComponent class).
')
So, back to the problem posed - to provide the client with a uniform interface to the leaf and composite elements of the tree structure. Obviously, to solve this problem, it is necessary to have a common interface that will describe both elementary and composite objects. Moreover, since The interface also describes composite objects; it must contain container methods — add, remove, get to add, remove, and get an object from the container. Moreover, these methods must be parameterized thereby the common interface. Thus, it automatically becomes possible to add to the container not only elementary objects but also other containers.

All objects of the tree structure (leaf and compound) must implement this uniform interface, and the composite objects override the add, remove, get operations and the leaf objects simply ignore them.

Practical task


Let's write the simplest adder of expressions using the “Linker” pattern. The adder should not be engaged in the analysis of expressions, it should only describe and implement a tree structure for more convenient evaluation of expressions.

I deliberately did not take the classic examples with the designers or the user interface, so that the reader does not have impressions about the narrow directionality of the pattern.

Class diagram


First, a few comments. In this example, I treat the concept of a container somewhat differently. Let's just say, I projected the classic concept of a container onto the subject domain - the evaluation of expressions. My “container” behaves somewhat differently than the classic one. Instead of the remove () method, SubExpression has a sub () method, which actually removes the container, but only in its own way. Since this is still an adder, the sub () method, like add (), adds a subexpression into the container, but with the opposite sign, thereby realizing the subtraction.



Consider a chart. The SubExpression interface describes a uniform interface for all objects of the tree structure, which, by the way, are not many — integers (IntegetValue), real numbers (FloatValue), and Expression. It is obvious that all numbers are leaf objects and they do not implement container methods, and the expression is just the opposite - a container.

Java implementation


There is no FloatValue class code in the implementation.
//
public interface SubExpression {

public Number value ();

public void add(SubExpression expr);
public void sub(SubExpression expr);
public SubExpression getSubExpression( int index);
}

// -
public class IntegerValue implements SubExpression {

private Integer value ;

public IntegerValue(Integer value ) {
this . value = value ;
}

@Override
public void add(SubExpression expr) {
throw new UnsupportedOperationException();
}

@Override
public SubExpression getSubExpression( int index) {
throw new UnsupportedOperationException();
}

@Override
public void sub(SubExpression expr) {
throw new UnsupportedOperationException();
}

@Override
public Number value () {
return value ;
}
}

import java.util. ArrayList ;
import java.util. List ;

// -
public class Expression implements SubExpression {

private List <SubExpression> exprs;

public Expression(SubExpression ... exprs) {
this .exprs = new ArrayList <SubExpression>();
for (SubExpression expr: exprs) {
this .exprs.add(expr);
}
}

@Override
public void add(SubExpression expr) {
exprs.add(expr);
}

@Override
public void sub(SubExpression expr) {
if (expr instanceof IntegerValue) {
exprs.add( new IntegerValue(-1*expr. value ().intValue()));
} else {
exprs.add( new FloatValue(-1*expr. value ().floatValue()));
}

}

@Override
public SubExpression getSubExpression( int index) {
return exprs. get (index);
}

@Override
public Number value () {
Number result = new Float(0);

for (SubExpression expr: exprs) {
result = result.floatValue() + expr. value ().floatValue();
}

return result;
}
}

//
public class Main {

public static void main( String [] args) {
// - 20 - (5-2) - (11+6)
// 20 - a - b
SubExpression expr = new Expression();

SubExpression a = new Expression( new IntegerValue(5), new IntegerValue(-2));
SubExpression b = new Expression( new IntegerValue(11), new IntegerValue(6));

expr.add( new IntegerValue(20));
expr.sub(a);
expr.sub(b);

System. out .println(expr. value ());
}
}

* This source code was highlighted with Source Code Highlighter .


I hope I managed to convey to you the idea of ​​a pattern.

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


All Articles