Decorator is a Structural Design Pattern with the following object structural:
The GoF (Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides) book describes the decorator pattern as a design pattern that "Allows for the dynamic wrapping of objects in order to modify their existing responsibilities and behaviours."
The decorator pattern is a structural pattern meant to extend the behavior of an object dynamically without inheritance via subclassing. In a simple and direct logic, we have an object, and at run-time, we add to it more tasks to accomplish. We can call this object the target-object, and it can provide from a previous decoration. This pattern allows us to create Matryoshka dolls effect using objects; the most "inner" object is the initial-object (undecorated object) and the most "outer" object is the currently target-object.
The decorator pattern is a structural pattern meant to extend the behavior of an object dynamically without inheritance via subclassing. In a simple and direct logic, we have an object, and at run-time, we add to it more tasks to accomplish. We can call this object the target-object, and it can provide from a previous decoration. This pattern allows us to create Matryoshka dolls effect using objects; the most "inner" object is the initial-object (undecorated object) and the most "outer" object is the currently target-object.
Decorator pattern in plain code
In plain
code we can imagine the following example: we have a standard web hosting
service shaped via an interface and the standard (initial) implementation. The
interface represents a simple contract:
public
interface Hosting {
public String getSubscriptionItems(); //
standard items: (e.g. email account, unlimited traffic)
public int getSubscriptionPeriod(); // client
subscribe for a limited period (number of months)
public double getSubscriptionPrice(); //
client subscription price
}
Now, the
standard web hosting subscriptions (objects) are obtained via WebHosting class:
public class
WebHosting implements Hosting {
public String items;
public int period;
public double price;
public WebHosting(String items, int period,
double price) {
this.items = items;
this.period = period;
this.price = price;
}
@Override
public int getSubscriptionPeriod() {
return period;
}
@Override
public double getSubscriptionPrice() {
return price;
}
@Override
public String getSubscriptionItems() {
return items;
}
}
Obviously,
now we can create web hosting objects (e.g. below we have a subscription for a
web hosting with one email account, unlimited traffic, 12 month at $25):
Hosting
hosting = new WebHosting("email account | unlimited traffic", 12,
25.0);
This is
the initial-object (currently target-object), or the object that follows to be
decorated. Let's suppose that we want to add some extra items, like SEO
support, SSH, etc. Each of these items is optional, they have their own period
of time and they alter the total price of the subscription. So, each of these
items needs to be considered as an "add-on" to the standard web
hosting, they decorate the hosting object.
We start
the decorator pattern implementation with an abstract class that implements the
Hosting interface
and overrides the methods common to all extra options - this will save us to
override these methods in each implementation (since the price is calculated
differently by each extra option, we simply keep it abstract):
public
abstract class ExtraHosting implements Hosting {
protected Hosting hosting;
protected String items;
protected int period;
protected double price;
public ExtraHosting(Hosting hosting, String
items, int period, double price) {
this.hosting = hosting;
this.items = items;
this.period = period;
this.price = price;
}
@Override
public abstract double getSubscriptionPrice();
@Override
public int getSubscriptionPeriod() {
return hosting.getSubscriptionPeriod();
}
@Override
public String getSubscriptionItems() {
return hosting.getSubscriptionItems() +
" | " + this.items;
}
}
Further,
we can create the concrete decorators by extending the ExtraHosting. For example, we have the SEOExtraHosting, which
adds a charge (via SEOExtraHosting#getSubscriptionPrice()) and the SEO
item (via ExtraHosting# getSubscriptionItems()):
public class
SEOExtraHosting extends ExtraHosting {
public SEOExtraHosting(Hosting hosting, String
items, int period, double price) {
super(hosting, items, period, price);
}
@Override
public double getSubscriptionPrice() {
return
hosting.getSubscriptionPrice() + this.price * period;
}
}
The same
principle can be applied for adding the SSH item:
public class
SSHExtraHosting extends ExtraHosting {
private final int EXTRA_SSH_TAX = 3;
public SSHExtraHosting(Hosting hosting, String
items, int period, double price) {
super(hosting, items, period, price);
}
@Override
public double getSubscriptionPrice() {
return hosting.getSubscriptionPrice() +
this.price * period + EXTRA_SSH_TAX;
}
}
Of course,
you can add more decorators by yourself.
Now that
the decorator pattern has been implemented to add extra items, you can
test your
implementation:
Hosting hosting = new WebHosting("email account |
unlimited traffic", 12, 25.0);
hosting = new
SEOExtraHosting(hosting, "SEO", 7, 5.0);
hosting = new
SSHExtraHosting(hosting, "SSH", 2, 3.0);
The
decorated hosting will look like this:
System.out.println(hosting.getSubscriptionItems()
+ "; " + hosting.getSubscriptionPeriod() + " months" +
"; $" + hosting.getSubscriptionPrice());
email
account | unlimited trafic | SEO | SSH; 12 months; $69.0
Decorator pattern in JSF
JSF uses the decorator pattern for
providing a flexible mechanism meant to extend the default JSF artifacts. For
example, default artifacts as resource handler, view handler, global exception
handler, partial view context, visit context, external context, navigation
handler, lifecycle, application, VDL, etc can be easily decorated by following
some simple rules. Mainly, you have to know that a custom implementation
receives the reference to the default implementation passed through its one
argument constructor. A custom implementation may overrides only some
functionality and delegates the rest of the functions to the default
implementation. Starting with JSF 2.0 and 2.2 almost all JSF artifacts comes
with a wrapper class. So, a custom artifact, consist in extending a wrapper class
of the artifact contract. The wrapper is just a simple implementation of the
artifact contract and is very useful to alter an existing instance of that
artifact (usually, an instance of the standard JSF artifact). Practically, the developer will override only those methods that he
want to alter their behavior.
Below you
can see several examples of decorating JSF artifacts:
·
JSF application artifact
public class
ApplicationDecorator
extends ApplicationWrapper {
private Application application; // the
target-object to be decorated
public ApplicationDecorator(Application
application) {
this.application = application;
}
@Override
public Application getWrapped() {
return application; // return the
target-object without decorations
}
// override here the application needed
functionalities
}
·
JSF VDL artifact
public class
VDLDecorator
extends ViewDeclarationLanguageWrapper {
private ViewDeclarationLanguage
viewDeclarationLanguage; // the target-object to be decorated
public VDLDecorator(ViewDeclarationLanguage
viewDeclarationLanguage) {
this.viewDeclarationLanguage =
viewDeclarationLanguage;
}
@Override
public ViewDeclarationLanguage getWrapped() {
return viewDeclarationLanguage; // return the
target-object without decorations
}
// override
here the VDL needed functionalities
}
·
JSF lifecycle artifact
public class
LifecycleDecorator
extends LifecycleWrapper {
private Lifecycle lifecycle; // the
target-object to be decorated
public LifecycleDecorator(Lifecycle lifecycle)
{
this.lifecycle = lifecycle;
}
@Override
public Lifecycle getWrapped() {
return lifecycle; // return the target-object
without decorations
}
// override
here the lifecycle needed functionalities
}
·
JSF flash artifact
public class
FlashDecorator
extends FlashWrapper {
private Flash flash; // the target-object to
be decorated
public FlashDecorator(Flash flash){
this.flash = flash;
}
@Override
public Flash getWrapped() {
return this.flash; // return the
target-object without decorations
}
// override here the flash needed
functionalities
}
Some real
cases can be seen in OmniFaces source code.
A very nice example consist in the ActionURLDecorator which is
a "cascade" decorator for view handler, application and faces
context.
You may
also want to check: RestorableViewHandler, TemporaryViewFacesContext, ValueToInvokeElContext, ValueToInvokeElResolver.
Read further: JSF and Decorator pattern - part II (CDI @Decorator and @Delegate)
Read further: JSF and Decorator pattern - part II (CDI @Decorator and @Delegate)
Niciun comentariu :
Trimiteți un comentariu