The decorator decorates a single object at the same time delegating its methods and hence modifying its behavior. Consider an interface Text with a single method String asString(); an example classes such as TrimmedText, UpperCaseText, and LowerCaseText.
The composite is a container of zero to n objects and just delegates behavior to its children.
These, however, can be combined into something called a composite decorator, which would be ConcatenatedText with respect to the previous examples.
Here is some code that proves the composite class can also act as a decorator and be decorated itself:
interface Text {
String asString();
// just a static helper to construct an instance of Text quickly
static Text of(String text) {
return () -> text;
}
}
// 1st decorator
@AllArgsConstructor
static class TrimmedText implements Text {
Text text;
@Override
public String asString() {
return text.asString().trim();
}
}
// 2nd decorator
@AllArgsConstructor
static class UpperCaseText implements Text {
Text text;
@Override
public String asString() {
return text.asString().toUpperCase();
}
}
// composite decorator
@AllArgsConstructor
static class ConcatenatedText implements Text {
List<Text> texts;
public void add(String text) {
texts.add(Text.of(text));
}
@Override
public String asString() {
return texts.stream().map(Text::asString).collect(Collectors.joining(", "));
}
}
@Test
void foo() {
Text trimmedUpperCaseText = new TrimmedText(new UpperCaseText(Text.of(" a b c ")));
assertThat(trimmedUpperCaseText.asString(), is("A B C"));
ConcatenatedText concatenatedText = new ConcatenatedText(new ArrayList<>(List.of(
new UpperCaseText(Text.of(" a ")),
new TrimmedText(Text.of(" b ")))));
concatenatedText.add("c");
Text refinedText = new TrimmedText(concatenatedText);
assertThat(refinedText.asString(), is("A , b, c")
}