Another design pattern that I will describe will be the title decorator. This pattern can be easily imagined as an implementation of the work of an interior decorator who, getting an unfinished apartment, decorates it with various gadgets. As a result, we get the same apartment, but it is already modified. What does it look like in programming? I invite you to read.
Table of content
Let's present the problem
The decorator pattern assumes the existence of two kinds of classes, which we will colloquially call the decorated and decorating class. In the example of an interior decorator, the class will decorate the apartment, and the class will decorate the ornament, or the decorative element that decorates the apartment. Every action performed by the decorator in the apartment causes that the apartment is still an apartment, but gains new functionalities.
At this point, one could directly think that inheritance is ideally suited to this mechanism. The problem is that each decorator has a palette of beaten paths and elements to use in his work. After all, why reinvent the wheel for each project? Creating the final project each time would require the decorator to write many classes, each of which would add one effect to the apartment, which, with many projects carried out by him, would multiply a lot of classes that would be difficult to reuse. Let's see an example below:
flat 1
class Mieszkanie {
int iloscScian = 4;
}
class MieszkanieJasne
extends Mieszkanie{
String kolor = "biały";
}
class MieszkanieBialeDuze
extends MieszkanieBiale{
double powierzchniaM2 = 120;
}
class MieszkanieBialeDuzeZBalkonem
extends MieszkanieBialeDuze{
boolean balkon = true;
}
flat 2
class Mieszkanie {
int iloscScian = 4;
}
class MieszkanieCiemne
extends Mieszkanie{
String kolor = "czarny";
}
class MieszkanieCiemneMale
extends MieszkanieCiemne{
double powierzchniaM2 = 42;
}
class MieszkanieCiemneMaleZTarasem
extends MieszkanieCiemneMale{
boolean taras = true;
}
Note that if we only wanted to create a small bright apartment with a terrace, we can only use the MieszkanieJasne class, while the remaining 2 should be added. This is not a convenient solution. Additionally, there is a problem when we want to compare two objects with each other. Note that both of them are of different types, so they cannot be compared to each other.
Let's solve the problem
An ideal solution for an interior decorator would be if he could compose each apartment from ready-made components, and then, for his own understanding, assess the attractiveness of the solution based on a parameter. This is where the decorator pattern triumphantly comes in.
At the very beginning, let's look at the final piece of code on which an interior decorator works to create new compositions.
Mieszkanie piekneMieszkanie = new MieszkanieNowe(); //tworzymy nasze piękne mieszkanie
piekneMieszkanie = new Jasne(piekneMieszkanie); //dekorujemy mieszkanie jasnymi ścianami
piekneMieszkanie = new ZTarasem(piekneMieszkanie); //dodajemy aranżację tarasu
Note that we are constantly working on our originally created object and wrapping it with new new objects. We do not change the type of object because we are still using the Apartment class. Both the object decorated with a beautiful Apartment and the decorating objects are of the same type, which may seem not very intuitive, but they offer great possibilities. The saved solution can be represented graphically as follows:
For type compatibility, all classes extend the base abstract class Apartment:
package com.company;
public abstract class Mieszkanie {
String opis; //zawiera opis dodawanej funkcjonalnosci
public String getOpis() {
return opis;
}
public abstract int indeksWartosci(); //zwraca wartosc wspolczynnika atrakcyjnosci mieszkania
}
On this basis, we create our decorated class New Apartment (later you can create similarly other classes, such as Single-family House, Seregowiec, etc.):
package com.company;
public class MieszkanieNowe extends Mieszkanie { //rozszerzamy klase Mieszkanie
public MieszkanieNowe() {
opis = "Mieszkanie z rynku pierwotnego";
}
@Override
public int indeksWartosci() {
return 50;
}
}
In this way, we created a decorated class which is the starting point of the entire composition. Now we'll move on to creating decorators, and we'll start with the fact that, as previously mentioned, the decorating class must be of the same type as the decorating class. So let's create a class:
package com.company;
public abstract class Ozdoba extends Mieszkanie{
public abstract String getOpis();
}
We will use this class to create appropriate decorating classes, such as:
package com.company;
public class Jasne extends Ozdoba {
Mieszkanie mieszkanie;
public Jasne(Mieszkanie mieszkanie) {
this.mieszkanie = mieszkanie;
}
@Override
public int indeksWartosci() {
return mieszkanie.indeksWartosci() + 4;
}
@Override
public String getOpis() {
return mieszkanie.getOpis() + " + Sciany jasne";
}
}
package com.company;
public class ZTarasem extends Ozdoba {
Mieszkanie mieszkanie;
public ZTarasem(Mieszkanie mieszkanie) {
this.mieszkanie = mieszkanie;
}
@Override
public int indeksWartosci() {
return mieszkanie.indeksWartosci() + 10;
}
@Override
public String getOpis() {
return mieszkanie.getOpis() + " + Taras";
}
}
Note that in the constructor of both classes I pass the existing apartment object already decorated in some way and in the methods indexWartosci () and getOpis () I add new data to the previous object.
We just created a simple application implementing the decorator pattern. Finally, let's see how our application will work after putting together all the classes in main:
package com.company;
public class Main {
public static void main(String[] args) {
Mieszkanie piekneMieszkanie = new MieszkanieNowe(); //tworzymy nasze piękne mieszkanie
piekneMieszkanie = new Jasne(piekneMieszkanie); //dekorujemy mieszkanie jasnymi ścianami
piekneMieszkanie = new ZTarasem(piekneMieszkanie); //dodajemy aranżację tarasu
System.out.println("Indeks wartości: " + piekneMieszkanie.indeksWartosci() + ", zawiera: " + piekneMieszkanie.getOpis());
}
}
The program output looks like this:
Indeks wartości: 64, zawiera: Mieszkanie z rynku pierwotnego + Sciany jasne + Taras
Process finished with exit code 0
Note that now it is very easy to add new classes, both decorated and decorated, because each time we can safely use previously written classes without worrying about their mutual compatibility.
Finally, I will present the definition of the decorator pattern, which should be more understandable to you after reading this article:
In the above definition, it is also worth paying attention to the word dynamic , because nothing prevents our interior decorator from implementing a solution based on artificial intelligence in the future. It would collect information about the owner of the apartment and on this basis, while the program is running, it would simulate how the apartment could be arranged for the client.
I hope that my article convinced you and gave you understanding about the decorator pattern, and if you have any comments, feel free to comment or contact us via the form.