avatarEmanuel Trandafir

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

4706

Abstract

pan class="hljs-title function_">email</span>()); }</pre></div><p id="b682">Additionally, we can add some sort of caching for the account, to avoid retrieving it twice. For simplicity, we’ll implement something very simple, using a <a href="https://www.java67.com/2013/02/10-examples-of-hashmap-in-java-programming-tutorial.html"><i>HashMap</i></a><i>:</i></p><div id="823b"><pre><span class="hljs-keyword">private</span> final <span class="hljs-title class_">Map</span><<span class="hljs-title class_">String</span>, <span class="hljs-title class_">Account</span>> cachedAccounts = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span><>();

<span class="hljs-built_in">void</span> <span class="hljs-title function_">sendPromotionalEmail</span>(<span class="hljs-params"><span class="hljs-built_in">String</span> username</span>) { <span class="hljs-keyword">if</span> (username.<span class="hljs-title function_">length</span>() <= <span class="hljs-number">5</span>) { <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">IllegalArgumentException</span>(<span class="hljs-string">"the username should be at least 5 characters long, but %s has less then this"</span>.<span class="hljs-title function_">formatted</span>(username)); } <span class="hljs-title class_">Account</span> account; <span class="hljs-keyword">if</span> (!cachedAccounts.<span class="hljs-title function_">containsKey</span>(username)) { cachedAccounts.<span class="hljs-title function_">put</span>(username, accounts.<span class="hljs-title function_">username</span>(username)); } account = cachedAccounts.<span class="hljs-title function_">get</span>(username); <span class="hljs-comment">// ...</span> <span class="hljs-title function_">sendEmail</span>(account.<span class="hljs-title function_">email</span>()); }</pre></div><p id="fad7">Let’s pause here for now. As we can see, this method has the potential to quickly become more complex. Additional features, which may not be directly related to the <i>sendPromotionalEmail</i> method can begin to accumulate, making it more difficult to read and comprehend. This can lead to obscured business logic, impeding code maintenance and future development efforts.</p><h2 id="dd23">The Solution</h2><p id="2efc">The solution is to extract small bits of code from here and move them to a more suitable place, such as <i>AccountsRepo</i>. Unfortunately though, sometimes, this interface can be provided by a library or framework, or you simply wouldn't want to add this validation there: for these cases, we can apply the <i>decorator</i> pattern.</p><p id="8783">To <i>decorate </i>this, we should declare a new interface that wraps the original one and enriches the call with some additional logic: for instance, the validation:</p><div id="6f99"><pre><span class="hljs-keyword">class</span> <span class="hljs-title class_">ValidAccountsRepo</span> <span class="hljs-title">implements</span> <span class="hljs-title">AccountsRepo</span> { <span class="hljs-comment">// constructor</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> AccountsRepo <span class="hljs-keyword">actual</span>;

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> Account username(String username) {
    <span class="hljs-keyword">if</span> (username.length() &lt;= <span class="hljs-number">5</span>) {
        <span class="hljs-keyword">throw</span> new IllegalArgumentException(<span class="hljs-string">"the username should be at least 5 characters long, but %s has less then this"</span>.formatted(username));
    }
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">actual</span>.username(username);
}

}</pre></div><p id="172c">Perfect, let’s do the same for the caching now:</p><div id="9129"><pre><span class="hljs-keyword">class</span> <span class="hljs-title class_">CachedAccountsRepo</span> <span class="hljs-title">implements</span> <span class="hljs-title">AccountsRepo</span> { <span class="hljs-comment">// constructor</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> AccountsRepo <span class="hljs-keyword">actual</span>;

<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Map&lt;String, Account&gt; cachedAccounts = new HashMap&lt;&gt;();

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> Account username(String username) {
    <span class="hljs-keyword">if</span> (!cach

Options

edAccounts.containsKey(username)) { cachedAccounts.put(username, <span class="hljs-keyword">actual</span>.username(username)); } <span class="hljs-keyword">return</span> cachedAccounts.<span class="hljs-keyword">get</span>(username); } }</pre></div><p id="6dca">We could continue down this path indefinitely, as there are many possible use cases. For instance, we could set a timeout, initiate a read-only transaction, or add logging information to data queries. However, let’s keep the momentum going by adding one more decorator and proceeding with our solution:</p><div id="c6df"><pre><span class="hljs-meta">@Slf4j</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">VerboseAccountsRepo</span> <span class="hljs-title">implements</span> <span class="hljs-title">AccountsRepo</span> { <span class="hljs-comment">// constructor</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> AccountsRepo <span class="hljs-keyword">actual</span>;

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> Account username(String username) {
    log.info(<span class="hljs-string">"retrieving the user: "</span> + username);
    <span class="hljs-keyword">var</span> user = <span class="hljs-keyword">actual</span>.username(username);
    log.info(<span class="hljs-string">"retrieved data: "</span> + user);
    <span class="hljs-keyword">return</span> user;
}

}</pre></div><p id="9b2a">Finally, let’s use these decorators in our service by wrapping the actual class that talks to the database and creating a nested structure:</p><div id="1b0c"><pre><span class="hljs-keyword">class</span> <span class="hljs-title class_">NotificationService</span> { <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> AccountsRepo accounts;

<span class="hljs-keyword">public</span> <span class="hljs-title function_">NotificationService</span><span class="hljs-params">(AccountsRepo accounts)</span> {
    <span class="hljs-built_in">this</span>.accounts = <span class="hljs-keyword">new</span> <span class="hljs-title class_">VerboseAccountsRepo</span>(
        <span class="hljs-keyword">new</span> <span class="hljs-title class_">CachedAccountsRepo</span>(
            <span class="hljs-keyword">new</span> <span class="hljs-title class_">ValidAccountsRepo</span>(
                accounts
            )
        )
    );
}

<span class="hljs-keyword">void</span> <span class="hljs-title function_">sendPromotionalEmail</span><span class="hljs-params">(String username)</span> {
    <span class="hljs-type">var</span> <span class="hljs-variable">account</span> <span class="hljs-operator">=</span> accounts.username(username);
    <span class="hljs-comment">// ...</span>
    sendEmail(account.email());
}

}</pre></div><p id="0208">Moreover, we can reuse this nested declaration in other parts of the application, but we can omit some of the decorators if needed. For Example, we can only discard the <i>cached</i> and <i>verbose </i>decorators for a component that receives many requests and the account data changes constantly.</p><h2 id="0c77">Conclusion</h2><p id="26dd">In this short article, we’ve explored the decorator pattern. We’ve learned how to implement it and when to use it, and we’ve seen the flexibility it provides.</p><p id="cb1b">We’ve learned that declaring interfaces for the key components of our application allows us to create decorators and comply with the open-closed principle.</p><figure id="f27d"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/0*u0ldrIfT-ZkAPe4D"><figcaption>Photo by <a href="https://unsplash.com/@ikukevk?utm_source=medium&amp;utm_medium=referral">Kevin Ku</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h1 id="d03a">Thank You!</h1><p id="0100">Thanks for reading the article and please let me know what you think! Any feedback is welcome.</p><p id="06bf">If you want to read more about clean code, design, unit testing, object-oriented programming, functional programming, and many others, make sure to check out <a href="https://readmedium.com/start-here-6d2b065a626">my other articles</a>. Do you like the content? Consider <a href="https://medium.com/@emanueltrandafir">following or subscribing</a> to the email list.</p><p id="93d8">Finally, if you consider becoming a Medium member and supporting my blog, here’s my <a href="https://medium.com/@emanueltrandafir/membership">referral</a>.</p><p id="93ab">Happy Coding!</p></article></body>

The Decorator Pattern in 3 Minutes

Let’s dive into the fascinating world of decorators.

The scuba diving suit is a good example of the decorator pattern: It allows the diver to breathe underwater by making the “breath()” method use a different supplier of Oxygen. Photo by Sebastian Pena Lambarri on Unsplash

Overview

The Decorator pattern is a structural design pattern in object-oriented programming that allows you to dynamically add behavior to an object without changing its class.

It involves creating a decorator class that wraps the original object and provides additional functionality by adding new methods or modifying existing ones. The decorator class implements the same interface as the original object, so the client code can use it transparently.

The Decorator pattern is useful when you need to add functionality to objects at runtime or when it’s not feasible to extend them using inheritance. It allows you to avoid creating a large number of subclasses to handle different combinations of behaviors, which can lead to code duplication and maintenance issues.

Instead, you can create small, focused decorator classes that add specific features to the base object. The Decorator pattern promotes the Open-Closed Principle, which states that classes should be open for extension but closed for modification

The Problem

Let’s consider we are using an interface to interact with the database for fetching account information based on username:

interface AccountsRepo {
    Account username(String username);
}

And we are using an implementation of this interface to send promotional emails to various users:

class NotificationService {
    private final AccountsRepo accounts;

    public NotificationService(AccountsRepo accounts) {
        this.accounts = accounts;
    }

    void sendPromotionalEmail(String username) {
        var account = accounts.username(username);
        // ...
        sendEmail(account.email());
    }
}

Now, let’s assume we would like to avoid the DB queries if possible: we know that the username must be at least five characters long, so we can avoid going to the database if the argument is not valid:

void sendPromotionalEmail(String username) {
    if (username.length() <= 5) {
        throw new IllegalArgumentException("the username should be at least 5 characters long, but %s has less then this".formatted(username));
    }
    var account = accounts.username(username);
    // ...
    sendEmail(account.email());
}

Additionally, we can add some sort of caching for the account, to avoid retrieving it twice. For simplicity, we’ll implement something very simple, using a HashMap:

private final Map<String, Account> cachedAccounts = new HashMap<>();

void sendPromotionalEmail(String username) {
    if (username.length() <= 5) {
        throw new IllegalArgumentException("the username should be at least 5 characters long, but %s has less then this".formatted(username));
    }
    Account account;
    if (!cachedAccounts.containsKey(username)) {
        cachedAccounts.put(username, accounts.username(username));
    }
    account = cachedAccounts.get(username);
    // ...
    sendEmail(account.email());
}

Let’s pause here for now. As we can see, this method has the potential to quickly become more complex. Additional features, which may not be directly related to the sendPromotionalEmail method can begin to accumulate, making it more difficult to read and comprehend. This can lead to obscured business logic, impeding code maintenance and future development efforts.

The Solution

The solution is to extract small bits of code from here and move them to a more suitable place, such as AccountsRepo. Unfortunately though, sometimes, this interface can be provided by a library or framework, or you simply wouldn't want to add this validation there: for these cases, we can apply the decorator pattern.

To decorate this, we should declare a new interface that wraps the original one and enriches the call with some additional logic: for instance, the validation:

class ValidAccountsRepo implements AccountsRepo {
    // constructor
    private final AccountsRepo actual;
    
    @Override
    public Account username(String username) {
        if (username.length() <= 5) {
            throw new IllegalArgumentException("the username should be at least 5 characters long, but %s has less then this".formatted(username));
        }
        return actual.username(username);
    }
}

Perfect, let’s do the same for the caching now:

class CachedAccountsRepo implements AccountsRepo {
    // constructor
    private final AccountsRepo actual;
    
    private final Map<String, Account> cachedAccounts = new HashMap<>();

    @Override
    public Account username(String username) {
        if (!cachedAccounts.containsKey(username)) {
            cachedAccounts.put(username, actual.username(username));
        }
        return cachedAccounts.get(username);
    }
}

We could continue down this path indefinitely, as there are many possible use cases. For instance, we could set a timeout, initiate a read-only transaction, or add logging information to data queries. However, let’s keep the momentum going by adding one more decorator and proceeding with our solution:

@Slf4j
class VerboseAccountsRepo implements AccountsRepo {
    // constructor
    private final AccountsRepo actual;
    
    @Override
    public Account username(String username) {
        log.info("retrieving the user: " + username);
        var user = actual.username(username);
        log.info("retrieved data: " + user);
        return user;
    }
}

Finally, let’s use these decorators in our service by wrapping the actual class that talks to the database and creating a nested structure:

class NotificationService {
    private final AccountsRepo accounts;

    public NotificationService(AccountsRepo accounts) {
        this.accounts = new VerboseAccountsRepo(
            new CachedAccountsRepo(
                new ValidAccountsRepo(
                    accounts
                )
            )
        );
    }

    void sendPromotionalEmail(String username) {
        var account = accounts.username(username);
        // ...
        sendEmail(account.email());
    }
}

Moreover, we can reuse this nested declaration in other parts of the application, but we can omit some of the decorators if needed. For Example, we can only discard the cached and verbose decorators for a component that receives many requests and the account data changes constantly.

Conclusion

In this short article, we’ve explored the decorator pattern. We’ve learned how to implement it and when to use it, and we’ve seen the flexibility it provides.

We’ve learned that declaring interfaces for the key components of our application allows us to create decorators and comply with the open-closed principle.

Photo by Kevin Ku on Unsplash

Thank You!

Thanks for reading the article and please let me know what you think! Any feedback is welcome.

If you want to read more about clean code, design, unit testing, object-oriented programming, functional programming, and many others, make sure to check out my other articles. Do you like the content? Consider following or subscribing to the email list.

Finally, if you consider becoming a Medium member and supporting my blog, here’s my referral.

Happy Coding!

Java
Python
JavaScript
Design Patterns
Programming
Recommended from ReadMedium