This is the story of "Sam, the billionaire". I like to translate all my stories in Java. So, you will also see Java version of this story.
Story begins...
Sam is owner of a Toy Company. Dolls made in his company are very famous.
Some of the popular Dolls
Java version-
Sam defines an interface
Doll which defines all required features which a doll should have (make, price & description).
Whenever a new type of doll is introduced it implements Doll interface. And it has to implement all the methods defined in it.
Next Step...
To take his company a step forward he wants to introduce new features in his dolls.
- Speech - Speaking doll, Singing doll, Nursery rhymes doll etc.
- Motion - Walking doll, Running doll, Spinning doll etc.
- Light - Doll with glowing eyes, mouth etc.
But he wants to keep these features customizable. Customer can choose one or more of these features and get a customized doll.
Sam decides to start making the dolls. So, he tries to figure out what all combinations are possible-
- Baby girl
- Baby girl which cries
- Baby girl which sings
- Baby girl which sings nursery rhymes
- Baby girl which walks
- Baby girl which cries and walks
- ...... And many more
Ok so, there are many possible combinations.
Java Version
To implement dolls with customizable features first thing that came into Sam's mind was inheritance. He made a class for each doll type which will implement Doll interface. Apart from implementing methods in Doll interface it added methods to give additional functionality specific to doll.
e.g. SpeakingDoll class will implement Doll interface and will have addSpeech() method for additional functionality which will be called from make() method.
Class Diagram will look something like this.(
Note- Not showing all classes due to space constraint)
There is a class explosion. Maintaining such a thing is going to be a big overhead.
Problems..
Large no. of possible combinations. So, many dolls will have to be built and Sam is not sure all of them will be sold.
Class Explosion. Large number of classes will make the code difficult to maintain.
If a new feature has to be added in future e.g. multiple languages again there will be a lot of possible combinations.
Not all functionality can be foreseen, If a new feature is added in future new classes will be required.
If we have to change voice of speaking dolls all dolls with speech will have to be changed.
Any change in the existing functionality will impact many classes.
Finding the Solution...
He calls his team for a solution. Luckily one of the team member suggests a perfect solution.
Use of Decorator Pattern.
Lets try to understand this solution-
Make a doll when there is an order. Take the basic doll e.g. Baby girl, Baby boy etc. based on the requirement and add features to it as per client's demand.
Add features dynamically at runtime.
Lets take example of Singing baby girl to understand the solution.
Baby girl doll is build in factory. Sound Engineer use this doll and add singing feature to it.
If any other feature is also required e.g. walking then this doll is passed to the motion engineer who adds the feature on the doll. So, one feature is built on the top of the other. Something like this.
JavaVersion-
As each feature has to be added at runtime we need a separate class for each feature e.g. SingingDoll with method addSinging() which will add the required feature.
Sound engineer required a baby girl doll to add singing feature on it similarly
SingingDoll object needs
BabyGirl object on the top of which it can add singing feature.
SingingDoll
has BabyGirl
- Using composition we can add additional features to an object.
In future requirement can change and client may want Baby boy doll or any other doll with singing feature. If we add an
object of type
Doll to SingingDoll we will not have to worry about the type of doll as all dolls implement Doll interface.
So, we can decide at runtime what type of doll we want and can add its object to SingingDoll
- Using inheritance we can change the type of an object at runtime.
So, using the power of composition and inheritance together we can add the functionality dynamically. Any feature class will look like this.
Works well for SingingDoll but in future we may want to add another feature on the top of it e.g. Crying
Design will be something like this in that case-
In that case, we will need CryingDoll class to wrap around object of SingingDoll class. i.e.
CryingDoll has SingingDoll. Singing Doll has BabyGirlDoll.
Like any Feature class CryingDoll class has object of type Doll. But we have to add SingingDoll object(another Feature).
If SingingDoll too implements Doll interface it will not be a problem.
If Feature class implements Doll interface we can add any Feature on the top of the other.
Now doll object in Feature class can be used to access the basic functionality of underlying object and any new feature can be added before or after it.
To make design more flexible we add a superclass FeatureDecorator which all feature classes will extend. Any additional behaviour can be added in subclass.
This is example of
Decorator Pattern in Java.
- To add feature to a component at runtime Decorator class is used.
- This class implements interface of the component and also has object having type of component's interface.
- So, object of any component can be added to a Decorator.
- Since Decorator also implements component's interface, one Decorator can also be added to other.
This is how Decorator Pattern works.
Decorator object
has another Decorator object or Component object.
Component object is at the core.
- Client has access to the outermost Decorator object.
- Decorator object have full access to the functionality of underlying Decorator or Component. Using its object it can call the functionality before or after its own functionality. This helps in adding or modifying underlying functionality.
This is how method call works in Decorator Pattern-
Client calls the method of outermost Decorator class. This method calls the method of underlying Decorator/Component using the object it has. It can use output of this method to add some additional behaviour to it.
Using this lets try to calculate the cost of a Singing Baby Doll-
Outermost Object is SingingDoll(Decorator). From cost() method of SingingDoll we can call doll.cost(). This will give cost of underlying doll object which is actually BabyDoll(Component). Now we can add additional cost of singing feature and return to the user.
Now that our pattern is ready its time to write some Java Code-
Main Interface-
Main components (Types of Dolls)-
It implements Doll interface and its methods.
BabyGirl implementation-
BabyBoy implementation-
Abstract Decorator class-
It was simple till this point. Now we need to add Decorator superclass so that we can add features dynamically.
This interface has an object of type Doll and it also implements Doll interface.
ConcreteDecorator class-
SingingDoll class extends this class.
Methods of SingingDoll calls method of doll object and add its own functionality before or after it.
Here functionality added by SingingDoll class is outlined in green color in each method.
SingingDoll object gets access to the underlying doll object functionality and state. It can add or modify it as per requirement.
Lets use this code to create a Singing Baby Girl doll.
Create object of BabyGirl and pass it to SingingDoll object.
output of
singing.make()-
Baby Girl is Ready
Singing feature added
We call make method of SingingDoll from which make method of underlying doll object(BabyGirl) is called.
Similarly, when we call cost() method we get cost of BabyGirl (100)+Singing(50)
150
If we change the underlying object to BabyBoy-
Methods of BabyBoy will be called.
output of
singing.make()-
Baby Boy is Ready
Singing feature added
when we call cost() method we get cost of BabyBoy (60)+Singing(50)
110
To add any new feature e.g. Crying to it Just need to pass singing object to the constructor of Crying class(which extends FeatureDecorator)
We can add as many features as we want. We can also have different types of decorators e.g. SpeechDecorator, MotionDecorator, LightDecorator etc.
But they should follow rules of Decorator pattern-
- Decorator class should be of same type as the component to which they add functionality. So, they implement the interface which the component implement.
- Decorator class wrap the object of the component and add functionality to it. So, they have an object of the component. Type of this object is Interface which component implement. This provide flexibility to add another Decorator on top (as all Decorators are also of same type)
- There can be many layers of Decorator wrapped around component. Each Decorator add or modify the existing functionality.
- Methods of Decorator call methods of component (using the object they have) either before or after their implementation. This adds or modifies the functionality.
Class Diagram of Decorator Pattern-
Decorator Pattern is a complicated pattern so it should be used wisely else it makes the code hard to understand and maintain.
In Java, I/O Stream uses Decorator Pattern.
Thanks to Decorator Pattern, Sam is a billionaire today.
visit ApexInformatix.com for Online/Classroom course on Java and other technologies