Design
principles:
SOLID
Principle
1. Single Responsibility Principle
“One
class should have one and only one responsibility”
We can
plenty of classes in all popular Java libraries which follow single
responsibility principle. For example, in log4j, we have different classes with
logging methods, different classes are logging levels and so on.
In given
example, we have two classes
Person
and Account
. Both
have single responsibility to store their specific information. If we want to
change state of Person then we do not need to modify the class Account
and
vice-versa.
2. Open Closed Principle
“Software
components should be open for extension, but closed for modification”
This
means that our classes should be designed such a way that whenever fellow
developers wants to change the flow of control in specific conditions in
application, all they need to extend our class and override some functions and
that’s it.
spring
framework has class
DispatcherServlet
. This
class acts as front
controller for String based web applications.
Please
note that apart from passing initialization parameters during application
startup, we can override methods as well to modify the behavior of target class
by extending the classes. For example, struts Action classes are extended to
override the request processing logic.
3. Liskov’s Substitution Principle
“Derived types must be completely substitutable for their
base types”
An
example of LSP can be custom
property editors in Spring framework. Spring provides
property editors to represent properties in a different way than the object
itself e.g. parsing human readable inputs from HTTP request parameters or
displaying human readable values of pure java objects in view layer e.g.
Currency
or URL
.
4. Interface Segregation Principle
“Clients
should not be forced to implement unnecessary methods which they will not use”https://howtodoinjava.com/best-practices/5-class-design-principles-solid-in-java/
Take an example. Developer Alex created an interface Reportable and added two methods generateExcel() and generatedPdf(). Now client ‘A’ wants to use this interface but
he intend to use reports only in PDF format and not in excel. Will he be able
to use the functionality easily?
NO. He will have to implement both the methods, out of which one
is extra burden put on him by designer of software. Either he will implement
another method or leave it blank. This is not a good design.
So what is the solution? Solution is to create two interfaces by
breaking the existing one. They should be like PdfReportable and ExcelReportable. This will give the flexibility to user to use
only required functionality only.
The best place to look for IPS examples is Java AWT event handlers
for handling GUI events fired from keyboard and mouse. It has different
listener classes for each kind of event. We only need to write handlers for
events, we wish to handle. Nothing is mandatory.
Some of the listeners are –
·
FocusListener
·
KeyListener
·
MouseMotionListener
·
MouseWheelListener
·
TextListener
·
WindowFocusListener
Anytime, we wish to handle any event, just find out corresponding
listener and implement it.
MouseMotionListenerImpl.java
|
public class MouseMotionListenerImpl implements MouseMotionListener
{
@Override
public void mouseDragged(MouseEvent e) {
//handler code
}
@Override
public void mouseMoved(MouseEvent e) {
//handler code
}
}
|
5. Dependency Inversion Principle
we
should design our software in such a way that various modules can be separated
from each other using an abstract layer to bind them together. In spring
framework, all modules are provided as separate components which can work
together by simply injected dependencies in other module.
Java Design Best Practices
8 signs of bad unit test cases
5 class design principles [S.O.L.I.D.] in java
Unit testing best practices
It’s overwhelmingly easy to write bad unit tests that add very little
value to a project while inflating the cost of code changes astronomically.
Learn how to write them correctly.
A new
approach for exception handling
A new approach uses static inner classes for every new exceptional
scenario. Worth reading for future software designers.
Java
exception handling using inner classes
Covering some well-known and some little known practices which you
must consider while handling exceptions in your next java programming
assignment.
Java executor framework best practices
Some best practices which you need to keep in mind while designing
your next multi-threaded application.
1.
Always run your java code against static analysis tools like PMD and FindBugs to look for deeper issues. They
are very helpful in determining ugly situations which may arise in future.
2.
Always cross check and better plan a code review
with senior guys to detect and possible deadlock or livelock in
code during execution
- In multi-threaded programs,
make a habit of catching errors too, not just exceptions. Sometimes
unexpected things happen and Java throws an error at you, apart from an
exception.
- Use a back-off switch, so if
something goes wrong and is non-recoverable, you don’t escalate the
situation by eagerly starting another loop. Instead, you need to wait
until the situation goes back to normal and then start again.
- Please note that the whole
point of executors is to abstract away the specifics of execution, so
ordering is not guaranteed unless explicitly stated.
5 Reasons You Should Consider Migrating Your Legacy Systems
There are several reasons for migrating from old legacy systems to
new advanced systems. Here are 5 of them.
13 best practices for writing spring configuration files
13 best practices for writing highly maintainable spring XML
configurations.
Java Serialization – Dos
and don’ts for correct serialization
1.
Java serialization incompatible changes
Deleting fields: If a
field is deleted in a class, the stream written will not contain its value.
When the stream is read by an earlier class, the value of the field will be set
to the default value because no value is available in the stream.
Moving classes up or down
the hierarchy– This cannot be allowed since the data in the
stream appears in the wrong sequence.
Changing a non-static field
to static or a non-transient field to transient – When
relying on default serialization, this change is equivalent to deleting a field
from the class.
Changing the declared type
of a primitive field –Earlier versions of the class attempting
to read the field will fail because the type of the data in the stream does not
match the type of the field.
Adding the writeReplace or readResolve method to a class is incompatible if the behavior would
produce an object that is incompatible with any older version of the class.
Changing a class from a non-enum type to an enum type or vice
versa since the stream
will contain data that is incompatible with the implementation of the available
class.
Changing a class from Serializable to Externalizable or vice-versa is an incompatible change since the stream
will contain data that is incompatible with the implementation of the available
class.
2.
Java serialization compatible changes
Adding fields –field
in the object will be initialized to the default value for its type. If
class-specific initialization is needed, the class may provide a readObject
method that can initialize the field to non default values.
Adding classes – ince
there is no information in the stream from which to initialize the object, the
class’s fields will be initialized to the default values.
Removing classes – the
fields and objects corresponding to that class are read from the stream. Primitive
fields are discarded, but the objects referenced
by the deleted class are created, since they may be referred to later in the
stream.
Adding
writeObject/readObject methods – If the version reading the stream
has these methods then readObject is expected, as usual, to read the required
data written to the stream by the default serialization. It should
call defaultReadObject first before reading any optional data. The writeObject
method is expected as usual to call defaultWriteObject to write the required
data and then may write optional data.
Removing
writeObject/readObject methods –
Adding java.io.Serializable – This
is equivalent to adding types. There will be no values in the stream for this
class so its fields will be initialized to default values. The support for
subclassing nonserializable classes requires that the class’s super type have a
no-arg constructor and the class itself will be initialized to default values.
If the no-arg constructor is not available, the
InvalidClassException
is
thrown.
Changing the access to a
field –
The access
modifiers public, package, protected, and private
have no effect on the ability of serialization to assign values to the fields.
Changing a field from
static to non-static or transient to non transient –
3.
serialVersionUID
Always include it as a
field- for example: “private static final long
serialVersionUID = 7526472295622776147L; ” include this field even in the first
version of the class, as a reminder of its importance.
Do not change the value of
this field in future versions, unless you are knowingly making changes
4.
readObject() and writeObject() methods
Deserialization must be treated as any
constructor : validate
the object state at the end of deserializing – this
implies that readObject should almost always be implemented in Serializable
classes, such that this validation is performed.
5.
More serialization best practices
Use javadoc’s @serial tag to denote Serializable fields.
The .ser extension is conventionally used for files representing
serialized objects.
No static or transient fields undergo default serialization.
Extendable classes should not be Serializable, unless necessary.
Inner classes should rarely, if ever, implement Serializable.
Container classes should usually follow the style of Hashtable,
which implements Serializable by storing keys and values, as opposed to a large
hash table data structure.
6. Sample class following serialization best practices
7. Serialization and deserialization example
Java
serialization is the process of converting an object into
a stream of bytes so we can do stuff like store it on disk or send it over the
network. Deserialization is the reverse process – converting a stream of bytes
into an object in memory.
1. Java serialVersionUID Syntax
private
static
final
long
serialVersionUID = 4L;
Java serialization and deserialization example
Types of Design Patterns
There
are mainly three types of design patterns:
These design patterns are all about class instantiation or
object creation.
Creational design patterns are Factory Method, Abstract
Factory, Builder, Singleton, Object Pool and Prototype.
Factory Method
related to object
creation. In Factory pattern,
we create object without exposing the creation logic to client and the client
use the same common interface to create new type of object.
Factory
pattern introduces loose
coupling between classes which is the most important
principle one should consider and apply while designing the application
architecture.
Factory
pattern is most suitable where there is some complex object creation steps are
involved. To ensure that these steps are centralized and
not exposed to composing classes, factory pattern should be used.
Abstract factory pattern
Sequence Diagram
Car.java
|
public abstract class Car {
public Car(CarType model,
Location location){
this.model = model;
this.location = location;
}
protected abstract void construct();
private CarType model
= null;
private Location location
= null;
//getters
and setters
@Override
public String toString() {
return "Model- "+model + " built in
"+location;
}
}
|
This adds extra work of creating another enum for storing
different locations.
Location.java
|
public enum Location {
DEFAULT,
USA, ASIA
}
|
All car types will also have additional location property. We are writing only for the
luxury car. Same follows for small and sedan also.
LuxuryCar.java
|
public class LuxuryCar extends Car
{
public LuxuryCar(Location
location)
{
super(CarType.LUXURY, location);
construct();
}
@Override
protected void construct() {
System.out.println("Building luxury car");
//add accessories
}
}
|
So far we have created basic classes. Now let’s have different car
factories which is the core idea behind abstract factory pattern.
AsiaCarFactory.java
|
public class AsiaCarFactory
{
public static Car buildCar(CarType
model)
{
Car car = null;
switch (model)
{
case SMALL:
car = new SmallCar(Location.ASIA);
break;
case SEDAN:
car = new SedanCar(Location.ASIA);
break;
case LUXURY:
car = new LuxuryCar(Location.ASIA);
break;
default:
//throw some exception
break;
}
return car;
}
}
|
DefaultCarFactory.java
|
public class DefaultCarFactory
{
public static Car buildCar(CarType
model)
{
Car car = null;
switch (model)
{
case SMALL:
car = new SmallCar(Location.DEFAULT);
break;
case SEDAN:
car = new SedanCar(Location.DEFAULT);
break;
case LUXURY:
car = new LuxuryCar(Location.DEFAULT);
break;
default:
//throw some exception
break;
}
return car;
}
}
|
USACarFactory.java
|
public class USACarFactory
{
public static Car buildCar(CarType
model)
{
Car car = null;
switch (model)
{
case SMALL:
car = new SmallCar(Location.USA);
break;
case SEDAN:
car = new SedanCar(Location.USA);
break;
case LUXURY:
car = new LuxuryCar(Location.USA);
break;
default:
//throw some exception
break;
}
return car;
}
}
|
Well, now we have all 3 different Car factories. Now, we have to
abstract the way these factories are accessed.
CarFactory.java
|
public class CarFactory
{
private CarFactory() {
//Prevent instantiation
}
public static Car buildCar(CarType
type)
{
Car car = null;
Location location = Location.ASIA; //Read location
property somewhere from configuration
//Use location specific car factory
switch(location)
{
case USA:
car = USACarFactory.buildCar(type);
break;
case ASIA:
car = AsiaCarFactory.buildCar(type);
break;
default:
car = DefaultCarFactory.buildCar(type);
}
return car;
}
}
|
We are done with writing code. Now, let’s test the factories and cars.
TestFactoryPattern.java
|
public class TestFactoryPattern
{
public static void main(String[] args)
{
System.out.println(CarFactory.buildCar(CarType.SMALL));
System.out.println(CarFactory.buildCar(CarType.SEDAN));
System.out.println(CarFactory.buildCar(CarType.LUXURY));
}
}
|
Program output:
Console
|
Output: (Default location is Asia)
Building small car
Model- SMALL built in ASIA
Building sedan car
Model- SEDAN built in ASIA
Building luxury car
Model- LUXURY built in ASIA
|
Prototype design pattern
A prototype is a
template of any object before the actual object is constructed. In this design
pattern, an instance of actual object (i.e. prototype) is created on starting,
and thereafter whenever a new instance is required, this prototype is cloned to
have another instance.
Prototype Design Participants
1) Prototype :
This is the prototype of actual object.
2) Prototype registry :
This is used as registry service to have all prototypes accessible using simple
string parameters.
3) Client : Client
will be responsible for using registry service to access prototype instances.
Builder Pattern
The builder
pattern, as name implies, is an alternative way to construct complex
objects. This should be used only when you want to build different
immutable objects using same object building process.
In normal practice, if you want to make a immutable User class, then you must pass all five
information as parameters to constructor. It will look like this:
public User (String firstName, String lastName, int age, String phone,
String address){
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.phone = phone;
this.address = address;
}
|
Very good. Now what if only firstName and lastName are mandatory and rest 3
fields are optional. Problem !! We need more constructors.
public User (String firstName, String lastName, int age, String phone){
... }
public User (String firstName, String lastName, String phone,
String address){ ... }
public User (String firstName, String lastName, int age){
... }
public User (String firstName, String lastName){
... }
|
We will need some more like above. Still can manage? Now let’s
introduce our sixth attribute i.e. salary. Now it is problem.
One way it to create more constructors, and another is to loose
the immutability and introduce setter methods. You choose any of both options,
you loose something, right?
Here,
builder pattern will help you to consume additional attributes while retaining
the immutability of Use class.
And
below is the way, we will use the
UserBuilder
in
our code:
Singleton
where an
application wants to have one and only one instance of any class
Singleton with eager initialization
This is
a design pattern where an instance of a class is created much before it is
actually required.
public class EagerSingleton {
private static volatile EagerSingleton instance
= new EagerSingleton();
// private constructor
private EagerSingleton() {
}
public static EagerSingleton getInstance() {
return instance;
}
}
The
above method works fine, but it has one drawback. The instance is created
irrespective of it is required in runtime or not.
2. Singleton with lazy initialization
In
computer programming, lazy initialization is
the tactic of delaying the creation of an object, the calculation of a value,
or some other expensive process, until the first time it is needed.
3. Singleton with static block initialization
If you
have an idea of the class loading sequence, you can use the fact that static
blocks are executed during the loading of a class, even before the constructor
is called.
The
above code has one drawback. Suppose there are 5 static fields in a class and the
application code needs to access only 2 or 3, for which instance creation is
not required at all. So, if we use this static initialization, we will have one
instance created though it is required or not.
4. Singleton with bill pugh solution
Bill
Pugh was main force behind the java memory model changes.
His principle “Initialization-on-demand holder idiom” also
uses the static block idea, but in a different way. It suggest to use static
inner class.
5. Singleton using Enum
This
type of implementation employs the use of enum. Enum, as written in the java docs, provided implicit
support for thread safety and only one instance is guaranteed.
6. Add readResolve() to Singleton Objects
Our
singleton class is:
Let’s
say your application is distributed and it frequently serializes objects into
the file system, only to read them later when required. Please note that de-serialization
always creates a new instance. Let’s understand using an example:
Let’s
serialize this class and de-serialize it after making some changes:
Unfortunately,
both variables have different values of the variable “
i
”.
Clearly, there are two instances of our class. So, again we are in the same
problem of multiple instances in our application.
To solve
this issue, we need to include a
readResolve()
method
in our DemoSingleton
class. This method will be invoked when you
will de-serialize the object. Inside of this method, you must return the
existing instance to ensure a single instance application wide.
7. Add serialVersionUId to singleton objects
Untill
now, we have solved both of the problems of synchronization and serialization.
Now, we are just one step away from a correct and complete implementation. The
only missing part is a serial version id.
This is required in cases where your class structure changes
between serialization and deserialization. A changed class structure will cause
the JVM to give an exception in the de-serializing process.
java.io.InvalidClassException: singleton.DemoSingleton;
local class incompatible: stream classdesc serialVersionUID
= 5026910492258526905, local class serialVersionUID =3597984220566440782
at java.io.ObjectStreamClass.initNonProxy(Unknown Source)
at java.io.ObjectInputStream.readNonProxyDesc(Unknown
Source)
at java.io.ObjectInputStream.readClassDesc(Unknown Source)
at java.io.ObjectInputStream.readOrdinaryObject(Unknown
Source)
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.readObject(Unknown Source)
at singleton.SerializationTest.main(SerializationTest.java:24)
|
This problem can be solved only by adding a unique serial version
id to the class. It will prevent the compiler from throwing the exception by
telling it that both classes are same, and will load the available instance
variables only.
2.
Structural
These design patterns are about organizing different classes
and objects to form larger structures and provide new functionality.
Structural design patterns are Adapter, Bridge, Composite,
Decorator, Facade, Flyweight, Private Class Data and Proxy.
Adapter Design Pattern
Convert the interface of a class into another interface
clients expect. Adapter lets classes work together that couldn't otherwise
because of incompatible interfaces.
- Wrap an existing class with a
new interface.
- Impedance match an old
component to a new system
One reason has been the tribulation of designing something
new, while reusing something old. There is always something not quite right
between the old and the new.
Adapter is about creating an intermediary abstraction that
translates, or maps, the old component to the new system.
Check list
- Identify
the players: the component(s) that want to be accommodated (i.e. the
client), and the component that needs to adapt (i.e. the adaptee).
- Identify
the interface that the client requires.
- Design a
"wrapper" class that can "impedance match" the adaptee
to the client.
- The
adapter/wrapper class "has a" instance of the adaptee class.
- The
adapter/wrapper class "maps" the client interface to the adaptee
interface.
- The client
uses (is coupled to) the new interface
source code example
- Identify the desired interface.
- Design a "wrapper"
class that can "impedance match" the old to the new.
- The adapter/wrapper class
"has a" instance of the legacy class.
- The adapter/wrapper class
"maps" (or delegates) to the legacy object.
- The client uses (is coupled to)
the new interface.
interface
Bird
{
// birds implement Bird interface
that allows
// them to fly and make sounds
adaptee interface
public void fly();
public void makeSound();
}
class
Sparrow implements Bird
{
// a concrete implementation of bird
public void fly()
{
System.out.println("Flying");
}
public void makeSound()
{
System.out.println("Chirp
Chirp");
}
}
interface
ToyDuck
{
// target interface
// toyducks dont fly they just make
// squeaking sound
public void squeak();
}
class
PlasticToyDuck implements ToyDuck
{
public void squeak()
{
System.out.println("Squeak");
}
}
class
BirdAdapter implements ToyDuck
{
// You need to implement the
interface your
// client expects to use.
Bird bird;
public BirdAdapter(Bird
bird)
{
// we need
reference to the object we
// are
adapting
this.bird =
bird;
}
public void squeak()
{
// translate
the methods appropriately
bird.makeSound();
}
}
class
Main
{
public static void main(String
args[])
{
Sparrow
sparrow = new
Sparrow();
ToyDuck
toyDuck = new
PlasticToyDuck();
// Wrap a
bird in a birdAdapter so that it
// behaves
like toy duck
ToyDuck
birdAdapter = new
BirdAdapter(sparrow);
System.out.println("Sparrow...");
sparrow.fly();
sparrow.makeSound();
System.out.println("ToyDuck...");
toyDuck.squeak();
// toy duck
behaving like a bird
System.out.println("BirdAdapter...");
birdAdapter.squeak();
}
}
3.
Behavioral
Behavioral patterns are about identifying common
communication patterns between objects and realize these patterns.
Chain of responsibility, Command, Interpreter, Iterator,
Mediator, Memento, Null Object, Observer, State, Strategy, Template method,
Visitor
Strategy
Pattern
As always we will learn this pattern
by defining a problem and using strategy pattern to solve it.
Suppose we are building a game
“Street Fighter”. For simplicity assume that a character may have four moves
that is kick, punch, roll and jump. Every character has kick and punch moves, but roll and jump are
optional.
How would you model your
classes?
Suppose initially you use
inheritance and abstract out the common features in a Fighterclass and let other
characters subclass Fighter class.
Fighter class will we have default implementation of normal
actions. Any character with specialized move can override that action in its
subclass. Class diagram would be as follows:
What are the problems with above design?
What if a character doesn’t perform jump
move? It still inherits the jump behavior from superclass. Although you can
override jump to do nothing in that case but you may have to do so for many
existing classes and take care of that for future classes too.
What about an Interface?
Take a look at the following design:
It’s much cleaner. We took out some
actions (which some characters might not perform) out of Fighterclass and made
interfaces for them.
That way only characters that are
supposed to jump will implement the JumpBehavior.
What are the problems with
above design?
The main problem with the above
design is code reuse. Since there is no default implementation of jump and roll
behavior we may have code duplicity. You may have to rewrite the same jump
behavior over and over in many subclasses.
How can we avoid this?
What if we made JumpBehavior and RollBehavior classes
instead of interface? Well then we would have to use multiple inheritance that
is not supported in many languages due to many problems associated with it.
Here strategy pattern comes to our rescue. We
will learn what the strategy pattern is and then apply it to solve our problem.
Definition:
Wikipedia defines strategy pattern as:
“In computer programming,
the strategy pattern (also known as the policy pattern)
is a software design pattern that enables an algorithm’s
behavior to be selected at runtime. The strategy pattern
·
defines a family of algorithms,
·
encapsulates each algorithm, and
·
makes the algorithms interchangeable within that family.”
Here we rely on composition instead
of inheritance for reuse. The context would be the class that would require
changing behaviors. We can change behavior dynamically. Strategy is implemented
as interface so that we can change behavior without affecting our context.
Advantages:
1.
A
family of algorithms can be defined as a class hierarchy and can be used
interchangeably to alter application behavior without changing its
architecture.
2.
By
encapsulating the algorithm separately, new algorithms complying with the same
interface can be easily introduced.
3.
The
application can switch strategies at run-time.
4.
Strategy
enables the clients to choose the required algorithm, without using a “switch”
statement or a series of “if-else” statements.
5.
Data
structures used for implementing the algorithm are completely encapsulated in
Strategy classes. Therefore, the implementation of an algorithm can be changed
without affecting the Context class.
Disadvantages:
1.
The
application must be aware of all the strategies to select the right one for the
right situation.
2.
Context
and the Strategy classes normally communicate through the interface specified
by the abstract Strategy base class. Strategy base class must expose interface
for all the required behaviours, which some concrete Strategy classes might not
implement.
3.
In
most cases, the application configures the Context with the required Strategy
object. Therefore, the application needs to create and maintain two objects in
place of one.
Strategy pattern is also known as Policy Pattern. We
define multiple algorithms and let client application pass the algorithm to be
used as a parameter. One of the best example of strategy pattern is
Collections.sort()
method
that takes Comparator parameter. Based on the different implementations of
Comparator interfaces, the Objects are getting sorted in different ways.
PaymentStrategy.java
package
com.journaldev.design.strategy;
public interface
PaymentStrategy {
public void pay(int amount);
}
CreditCardStrategy.java
package
com.journaldev.design.strategy;
public class
CreditCardStrategy implements PaymentStrategy {
private String name;
private String cardNumber;
private String cvv;
private String dateOfExpiry;
public CreditCardStrategy(String nm, String ccNum,
String cvv, String expiryDate){
this.name=nm;
this.cardNumber=ccNum;
this.cvv=cvv;
this.dateOfExpiry=expiryDate;
}
@Override
public void pay(int amount) {
System.out.println(amount +"
paid with credit/debit card");
}
}
PaypalStrategy.java
package
com.journaldev.design.strategy;
public class
PaypalStrategy implements PaymentStrategy {
private String emailId;
private String password;
public PaypalStrategy(String email, String pwd){
this.emailId=email;
this.password=pwd;
}
@Override
public void pay(int amount) {
System.out.println(amount + "
paid using Paypal.");
}
}
Now our strategy pattern example algorithms are
ready. We can implement Shopping Cart and payment method will require input as
Payment strategy.
Item.java
package
com.journaldev.design.strategy;
public class Item {
private String upcCode;
private int price;
public Item(String upc, int cost){
this.upcCode=upc;
this.price=cost;
}
public String getUpcCode() {
return upcCode;
}
public int getPrice() {
return price;
}
}
ShoppingCart.java
package
com.journaldev.design.strategy;
import
java.text.DecimalFormat;
import
java.util.ArrayList;
import java.util.List;
public class ShoppingCart
{
//List of items
List<Item> items;
public ShoppingCart(){
this.items=new
ArrayList<Item>();
}
public void addItem(Item item){
this.items.add(item);
}
public void removeItem(Item item){
this.items.remove(item);
}
public int calculateTotal(){
int sum = 0;
for(Item item : items){
sum +=
item.getPrice();
}
return sum;
}
public void pay(PaymentStrategy paymentMethod){
int amount = calculateTotal();
paymentMethod.pay(amount);
}
}
Decorator Pattern
Definition:
The decorator pattern attaches additional responsibilities to an
object dynamically. Decorators provide a flexible alternative to subclassing
for extending functionality.
·
Each
component can be used on its own or may be wrapped by a decorator.
·
Each
decorator has an instance variable that holds the reference to component it
decorates(HAS-A relationship).
·
The
ConcreteComponent is the object we are going to dynamically decorate.
Advantages:
·
The
decorator pattern can be used to make it possible to extend (decorate) the
functionality of a certain object at runtime.
·
The
decorator pattern is an alternative to subclassing. Subclassing adds behavior
at compile time, and the change affects all instances of the original class;
decorating can provide new behavior at runtime for individual objects.
·
Decorator
offers a pay-as-you-go approach to adding responsibilities. Instead of trying
to support all foreseeable features in a complex, customizable class, you can
define a simple class and add functionality incrementally with Decorator
objects.
Disadvantages:
·
Decorators
can complicate the process of instantiating the component because you not only
have to instantiate the component, but wrap it in a number of decorators.
·
It
can be complicated to have decorators keep track of other decorators, because
to look back into multiple layers of the decorator chain starts to push the
decorator pattern beyond its true intent.
No comments:
Post a Comment
Note: only a member of this blog may post a comment.