2014/04/16

Pattern explore - Decorator

Abstract

Currently, I am revising pattern design on Software Develop. In order to have a deep understanding and goooooood memorable experiences on the patterns I revised, I am going to drop down the notes or codes in this blog. So, let's start on decorator

Decorator

Intent:
1. How to implement the objects that give the new functionality.
2. How to organize the objects for each special case.

To summarize, it is a very good pattern for chaining up a sequences of commands!

Use case


Imagine that you have follow commands on your hand:

ping(), iperf(), ssh(), get_bw(), telnet()

Then, your task is doing a bandwidth measurement between 2 network devices. So you will came following command sequences.

Get_BW_BY_SSH() if (ping()) // check if those 2 devices are alive host = ssh() // connect to host ret = host.iperf() // run iperf() get_bw(ret) //process results....
It sounds perfect. However, what if I need to support the connection protocol from ssh to telnet? Then, you need to add a switch on connection part.

Get_BW(protocol) if (ping()) // check if those 2 devices are alive host = switch(protocol){ "ssh": ssh() "telnet": telnet() } ret = host.iperf() // run iperf() get_bw(ret) //process results....
Not bad. OK! what if I need you to color the results if BW higher than certain thersold ?

Get_BW_BY_SSH_WITH_COLOR(protocol, thresold) if (ping()) // check if those 2 devices are alive host = switch(protocol){ "ssh": ssh() "telnet": telnet() } ret = host.iperf() // run iperf() bw = get_bw(ret) //process results.... if (bw > thresold) color(bw)
Obviously, We need a new function with each new requirements. Moreover, client sides will need to know which one they should call in order to fulfill what they need. I hopes I can convince you that this is a bad design.

You may argue that you can keep them in one function so that we don't need to add codes on client sides. However, your function must be full of "if-then-else" or "switch" for logic control. It will totally mess up your codes.

Decorator allows you to solve them properly. Consider the following codes:

Class Task { construtor: function (Task t) { this.task = Task; } do: function (val) {//your specific code} } Class Ping extends Task { construtor: function (Task t) { this.task = Task; } do: function (val) { ret = ping(); this.task.do(ret); } } Class Iperf extends Task { construtor: function (Task t) { this.task = Task; } do: function (val) { ret = val.iperf(); this.task.do(ret); } } Class GetBw extends Task { construtor: function (Task t) { this.task = Task; } do: function (val) { ret = get_bw(val); this.task.do(ret); } } Class Color extends Task { construtor: function (Task t) { this.task = Task; } do: function (val) { ret = this.task.do(val); color(ret); } } operation = new Ping(new SSH(new Iperf(new Color(new GetBw())))) operation()
Although this is an incomplete code, it shows how decorator works.


  • Specific Tasks are inherited from Class::Task and implement the same interface (function, method, whatever you like to call) do().
  • Instantiating those specific tasks by passing another tasks to their constructor as a refernce allows chaining the operation.
  • By modifying how to instantiate those instance, you can manage which order you would like to run those tasks. 
  • The client side know nothing about it since they are still calling operation().
  • The ordering of this.task.do() and your specify code is not matter. Just like Class::Color, it calls GetBw() before running its business logic.


See? That's the power of Decorator.

Updated on: 20/4/2014

沒有留言:

張貼留言