Decorator – helpful design pattern

0
156

Some time ago I found a design problem that the Decorator pattern helped me to resolve. Since that time I have used it a lot and now I find it as useful as Strategy and Factory patterns. 

The original issue was simple – I had a custom (but simple) logger that I had to use. The logger logged to the file. Then I received another requirement that I have to also log to the PowerShell console.

Initial state

The listing below shows MemoryLogger with three methods only. Instead of writing raw messages to the list, the LogLine object is created in order of future processing (it’s much easier to parse and sort LogLine objects because they carry additional information).

Additional requirement

Logging to the memory is not enough for us. We have to log to the PowerShell console too. Along with that, we have to add another method named „Progress” that will log the progress of the PS process. How to do this? Few options exist:

  1. Create a separate logger and use both in the calling code. This solution is flexible (you can use each logger whenever you want), but ugly (most probably each logging will require two lines of code in order to call two loggers).
  2. Rename MemoryLogger to Logger and add code that logs to PS in the same time. This solution is not flexible at all (you cannot use this logger without PowerShell context). But only one logger class is very tempting 🙂
  3. Use Decorator Pattern which I describe below.

Decorator Pattern

In order to introduce the pattern, we have to extract an interface from the MemoryLogger, create another class named PowerShellLogger that implements it too and connect these three types somehow 🙂

Note that only new/changed types are listed in the paste above.

As we can see, ILogger has been extracted. To the three existing methods, fourth has been added – Progress. This one is not used in the MemoryLogger and it may be a problem for us. Not now, because currently our MemoryLogger became a Decorator and when Progress is called, it just calls CmdletLogger.Progress method and that method does the work (displays progress in PS window).

So, is this everything? It seems so. We can instantiate logger in the client class constructor (the class must be derived from PSCmdlet class, I wrote about it):

And then use it as previously. No code has to be changed, ILogger object is used as MemoryLogger was used before.

But a problem emerged. Currently, we can use CmdletLogger on its own, but when we will try to use MemoryLogger as a standalone class, not a decorator, we will have to either pass null to its constructor (which will cause an exception soon) or change its internals to avoid it. Yet, we remember SOLID principles and the statement that „class should be open for extension, but closed for modification”. What do to then?

Open for extension, closed for modification

Let’s add another class then – NullLogger. It’s based on Null Object pattern.

Note, that changes in MemoryLogger are not necessary. I made them for convenience because then I can instantiate it with no object at all. Then internal logger will be of NullLogger class and each call to it will do nothing. Exactly as we wanted.

Summary

Decorating may be funny and profitable. When I want to extend some behaviour, I consider decorator as an option. Without it, conditional logic or other ugly things would be needed. One more example of how a decorator may be used – I prepared a file parser once. The problem was that this parser should parse different structures. Two JSON flavours and one plain text file. How I made it? With decorator of course. Take a look:

This time we chained 4 parsers. First, SimpleInvocationJsonParser tries to parse the file. This is because most of the input files are in this flavour. Then ComplexParser and ObsoleteParser is used. They are in order related to the frequency of occurrences. NullParser works exactly the same as NullLogger, it just returns an empty list to avoid NullReferenceExceptions later in the program.

I encourage you to think about it next time you have to write logic responsible for doing something of many versions.


Warning: A non-numeric value encountered in /home/platne/serwer17076/public_html/wp-content/themes/Newspaper/includes/wp_booster/td_block.php on line 1008