Monday, January 8, 2024

OOPS Design Principles

DRY (Don’t Repeat Yourself)

One of the most important OOPs Design Principles is DRY, as the name suggests DRY (don’t repeat yourself) means don’t write duplicate code, instead use Abstraction to abstract common things in one place. If you are using JDK 8 or later versions, you can implement the method in interfaces as well. If you have the same block of code in more than two places, consider making it a separate method. Even if you use a hard-coded value more than once, make them public final constant.

Composition Over Inheritance (COI)

COI is an acronym for Composition Over Inheritance. As the name implies, this principle emphasizes using Composition instead of Inheritance to achieve code reusability. Inheritance allows a subclass to inherit its superclass’s properties and behavior, but this approach can lead to a rigid class hierarchy that is difficult to modify and maintain. In contrast, Composition enables greater flexibility and modularity in class design by constructing objects from other objects and combining their behaviors. Additionally, the fact that Java doesn’t support multiple inheritances can be another reason to favor Composition over Inheritance.

Composition allows changing the behavior of a class at run-time by setting property during run-time, and by using Interfaces to compose a class, we use polymorphism, which provides flexibility to replace with better implementation at any time.

Difference between Composition and Inheritance

Now let’s understand the difference between Inheritance and Composition in a little bit more detail.

Static vs Dynamic

The first difference between Inheritance and Composition comes from a flexibility point of view. When we use Inheritance, we have to define which class you are extending in code. It cannot be changed at runtime, but with Composition you just define a Type which you want to use, which can hold it’s different implementation. In this sense, Composition is much more flexible than Inheritance.

Limited code reuse with Inheritance

As aforementioned, with Inheritance you can only extend one class, which means you code can only reuse just one class, not more than one. If you want to leverage functionalities from multiple classes, you must use Composition. For example, if your code needs authentication functionality, you can use an Authenticator, for authorization you can use an Authorizer etc.  But with Inheritance you just stuck with only class, why? Because Java doesn’t support multiple Inheritance. This difference between Inheritance vs Composition actually highlights a severe limitation of later reusability.

Unit Testing

This is in my opinion, the most important difference between Inheritance and Composition in OOP probably is the deciding factor whether to use Composition or Inheritance. When you design classes using Composition, they are easier to test because you can supply a mock implementation of the classes you are using. But when you design your class using Inheritance, you must need a parent class in order to test it’s child class. There is no way you can provide a mock implementation of the parent class.

Final Classes

This difference between them also highlights the other limitation of Inheritance. Composition allows code reuse even from final classes, which is not possible using Inheritance because you cannot extend final class in Java, which is necessary for Inheritance to reuse code.

Encapsulation

The last difference between Composition and Inheritance in Java in this list comes from Encapsulation and robustness point of view. Though both Inheritance and Composition allow code reuse, Inheritance breaks encapsulation because in case of Inheritance, subclass is dependent upon super class behavior. If parent classes change its behavior, then child class will also get affected. If classes are not properly documented and child class has not used the super class in a way it should be used, any change in super class can break functionality in the subclass.

The Composition provides a better way to reuse code and same time protect the class you are reusing from any of its clients, but Inheritance doesn’t offer that guarantee. However, sometimes Inheritance becomes necessary, mainly when you are creating class from the same family.

Programming for Interface not for Implementation

This OOPs Design Principles say that Always program for the interface and not for implementation; this will lead to flexible code that can work with any new implementation of the interface. But hold on for a min and go through below lines!

An interface might be a language keyword and even an interface might also be a design principle. Don’t confuse both! There are two rules to think of:

  • Use interfaces (the language keyword) if you have multiple concrete implementations.
  • Use interfaces (the design principle) to decouple your own system from external system. It refers to loose coupling between modules or systems.

Minimize Coupling

Coupling between modules/components is their degree of mutual interdependence; lower coupling is better. In other words, coupling is the probability that code unit “B” will “break” after an unknown change to code unit “A”.

Coupling refers to the degree of direct knowledge that one element has of another. In other words, how often do changes in class A force related changes in class B.

What is Tight Coupling?

In general, Tight coupling means the two classes often change together. In other words, if A knows more than it should about the way in which B was implemented, then A and B are tightly coupled. For example, if you want to change the skin, you would also have to change the design of your body as well because the two are joined together, they are tightly coupled. The best example of tight coupling is RMI (Remote Method Invocation).

What is Loose Coupling ?

In simple words, loose coupling means they are mostly independent. If the only knowledge that class A has about class B, is what class B has exposed through its interface, then class A and class B are said to be loosely coupled. In order to overcome from the problems of tight coupling between objects, spring framework uses dependency injection mechanism with the help of a POJO/POJI model. Needless to say, through dependency injection its possible to achieve loose coupling.

Maximize Cohesion

The Cohesion of a single module/component is the degree to which its responsibilities form a meaningful unit; higher cohesion is better. We should group the related functionalities as to share a single responsibility (e.g. in a class).

In general, Cohesion is most closely associated with making sure that a class is designed with a single, well-focused purpose. The more focused a class is, the cohesiveness of that class is more. The advantages of high cohesion is that such classes are much easier to maintain (and less frequently changed) than classes with low cohesion. Another benefit of high cohesion is that classes with a well-focused purpose tend to be more reusable than other classes.

Suppose we have a class that multiply two numbers, but the same class creates a pop-up window displaying the result. This is the example of low cohesive class because the window and the multiplication operation don’t have much in common. To make it high cohesive, we would have to create a class Display and a class Multiply. The Display will call Multiply’s method to get the result and display it. Therefore, this could be an example to develop a high cohesive solution within OOPs Design Principles.

KISS (Keep It Simple, Stupid)

The Keep it Simple, Stupid (KISS) principle states that most systems work the best if they are kept simpler rather than made complex. Therefore, we should consider the simplicity as a key goal in the design, and avoid the unnecessary complication.

The Keep it Simple, Stupid (KISS) principle is a reminder to keep your code simple and readable for humans. If your method handles multiple use-cases, split them into smaller methods. If it performs multiple functionalities, make multiple methods instead.

Furthermore, if a single method handles multiple functionalities, it will become long and bulky. A long method will be very hard to maintain for programmers. Consequently, bugs will be harder to find, and we might find ourselves violating other design principles as well. If a method does two things, you can’t call it to do just one of them, so you’ll obviously make another method.

Also, you should keep your code simple to be easily understood by other developers. Learning of  OOPs Design Principles can also help you to achieve this. For example, if a simple for loop does the job efficiently, you should not use a stream API unnecessarily.

Delegation Principles

Don’t do all stuff by yourself, delegate it to the respective classes. A classical example of the delegation design principle is equals() and hashCode() method in Java. In order to compare two objects for equality, we ask the class itself to make comparison instead of the Client class doing that check.

The key benefit of this OOPs Design Principles is no duplication of code and pretty easy to modify behavior. Event delegation is another example of this principle, where an event is delegated to handlers for handling.

Encapsulate What Changes

Only one thing is constant in the software field, and that is “Change,” So encapsulate the code you expect or suspect to be changed in the future. The benefit of this OOP Design principles is that It’s easy to test and maintain proper encapsulated code.

YAGNI (you aren’t gonna need it)

YAGNI stands for “you aren’t gonna need it”: don’t implement something until it is necessary. Always implement things when you actually need them, never when you just foresee that you need them. It leads to code bloat; the software becomes larger and more complicated. The YAGNI principle suggests that developers should avoid adding unnecessary functionality or code that is not currently needed. By focusing on the current requirements and keeping the code simple, developers can improve the overall quality of the software.

The YAGNI principle can help developers avoid wasting time on developing features that may never be used. Instead, developers should focus on delivering software that meets the current requirements and can be easily maintained and extended in the future if necessary.

 

 

Behavioral patterns

  1. Template Method Pattern
  2. Mediator Pattern
  3. Chain of Responsibility Pattern
  4. Observer Pattern
  5. Strategy Pattern
  6. Command Pattern
  7. State Pattern
  8. Visitor Pattern
  9. Iterator Pattern
  10. Interpreter Pattern
  11. Memento Pattern
Template Method Pattern

The Template Method Pattern describes the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.

 When to use the Template Design Pattern

  • To implement the invariant parts of an algorithm once and leave it up to subclasses to implement the behavior that can vary.
  • When common behavior among subclasses should be factored and localized in a common class to avoid code duplication. We should first identify the differences in the existing code and then separate the differences in new operations. Finally, replace the differing code with a template method that calls one of these new operations.

Usage in JDK

  • All non-abstract methods of java.io.InputStream, java.io.OutputStream, java.io.Reader and java.io.Writer. e.g. – java.io.InputStream#skip(), java.io.InputStream#read()
  • All non-abstract methods of java.util.AbstractList, java.util.AbstractSet and java.util.AbstractMap. e.g. – java.util.AbstractList#indexOf (), java.util.Collections#sort ().

 Mediator Design Pattern

Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.

We use the Mediator Design Pattern to provide a centralized communication medium between different objects in a system.

When to use the Mediator Pattern

  1. A set of objects communicates in well-defined but complex ways. The resulting interdependencies are unstructured and difficult to understand.
  2. Reusing an object is difficult because it refers to and communicates with many other objects.
  3. A behavior that’s distributed between several classes should be customizable without a lot of sub-classing.

Mediator Pattern in JDK

  • util.Timer class scheduleXXX () methods.
  • Java Concurrency Executor execute () method.
  • lang.reflect.Method invoke () method.

 Chain Of Responsibility Design Pattern

When there are more than one objects that can handle or fulfill a client request, the pattern recommends giving each of these objects a chance to process the request in some sequential order. Applying the pattern in such a case, each of these potential handlers can be arranged in the form of a chain, with each object having a reference to the next object in the chain.

 The first object in the chain receives the request and decides either to handle the request or to pass it on to the next object in the chain. The request flows through all objects in the chain one after the other until the request is handled by one of the handlers in the chain or the request reaches the end of the chain without getting processed.

When to use the Chain of Responsibility Pattern

Use Chain of Responsibility when

  1. More than one object may handle a request, and the handler isn’t known a priori. The handler should be ascertained automatically.
  2. You want to issue a request to one of several objects without specifying the receiver explicitly.
  3. The set of objects that can handle a request should be specified dynamically.

Chain of Responsibility Pattern Examples in JDK

  • util.logging.Logger#log ()
  • servlet.Filter#doFilter ()

Observer Design Pattern

In observer design pattern multiple observer objects registers with a subject for any change in notification. When the state of subject changes, it notifies the observers. Objects that listen or watch for change are called observers and the object that is being watched is called a subject. Pattern involved is also called as publish-subscribe pattern.

When to use the Observer Pattern

Use the Observer pattern in any of the following situations:

  1. When an abstraction has two aspects, one dependent on the other. Encapsulating these aspects in separate objects lets you vary and reuse them independently.
  2. When a change to one object requires changing others, and you don’t know how many objects need to be changed?
  3. When an object should be able to notify other objects without making assumptions about who these objects are. In other words, you don’t want tight coupling in these objects.

Usage in Java

  • util.EventListener in Swing
  • servlet.http.HttpSessionBindingListener
  • servlet.http.HttpSessionAttributeListener

 Strategy Design Pattern

The Strategy Design Pattern is useful when there is a set of related algorithms and a client object needs to be able to dynamically pick and choose an algorithm from this set that suits its current need. The Strategy pattern suggests keeping the implementation of each of the algorithms in a separate class. Each such algorithm encapsulated in a separate class is referred to as a Strategy. An object that uses a Strategy object is often referred to as a context object.

We use Strategy Pattern when we have multiple algorithms for a specific task and client decides the actual implementation to be used at runtime.

When to use the Strategy Design Pattern

Use the Strategy pattern when:

  • Many related classes differ only in their behavior as Strategies provide a way to configure a class with one of many behaviors.
  • You need different variants of an algorithm. For example, you might define algorithms reflecting different space/time trade-offs. Here, we can use Strategies when the implementation of these variants are as a class hierarchy of algorithms.
  • An algorithm uses data that clients shouldn’t know about. Use the Strategy pattern to avoid exposing complex, algorithm-specific data structures.
  • A class defines many behaviors, and these appear as multiple conditional statements in its operations. Instead of many conditionals, move related conditional branches into their own Strategy class.

Strategy Pattern in JDK

  • util.Comparator#compare ()
  • servlet.http.HttpServlet
  • servlet.Filter#doFilter ()

Command Design Pattern

The Command Pattern is a behavioral object design pattern. In the command pattern, a Command interface declares a method for executing a particular action. Concrete Command classes implement the execute () method of the Command interface, and this execute () method invokes the appropriate action method of a Receiver class that the Concrete Command class contains.

When to use the Command Design Pattern

Use the Command pattern when you want to:

  • Parameterize objects by an action to perform.
  • Structure a system around high-level operations built on primitives’ operations. Such a structure is common in information systems that support transactions. Moreover, the Command Pattern offers a way to model transactions. Commands have a common interface, letting you invoke all transactions the same way. The pattern also makes it easy to extend the system with new transactions.
  • The Command’s Execute operation can store state for reversing its effects in the command itself. The Command interface must have an added Un-execute operation that reverses the effects of a previous call to Execute. We can store the executed commands  in a history list. Also, we can achieve Unlimited-level undo and redo by traversing this list backwards and forwards calling Un-execute and Execute, respectively. It supports undo as well.

Command Pattern JDK Example

Runnable interface (java.lang.Runnable) and Swing Action (javax.swing.Action) uses command pattern.

Visitor Design Pattern

When we have to perform an operation on a group of similar kind of Objects, we use Visitor Pattern. With the help of visitor pattern, we can move the operational logic from the objects to another class. For example, think of a Shopping cart where we can add different type of items (Elements), when we click on the checkout button, it calculates the total amount that we need to pay. Now, we can have the calculation logic in item classes or we can move out this logic to another class using Visitor Pattern. So, using visitor pattern we can move out logics to another class. 

When to use the Visitor Design Pattern

Use the Visitor pattern when:

  • An object structure contains many classes of objects with differing interfaces, and you want to perform operations on these objects that depend on their concrete classes.
  • When we need to perform many distinct and unrelated operations on objects in an object structure, and we want to avoid “polluting” their classes with these operations. Visitor lets you keep related operations together by defining them in one class. When we need to share the object structure with many applications, use Visitor to put operations in just those applications that need them.
  • The classes defining the object structure rarely change, but you often want to define new operations over the structure. However, changing the object structure classes requires redefining the interface to all visitors, which is potentially costly. If the object structure classes change often, then it’s probably better to define the operations in those classes.

Visitor Design Pattern in JDK

  • lang.model.element.Element and javax.lang.model.element.ElementVisitor
  • lang.model.type.TypeMirror and javax.lang.model.type.TypeVisitor

State Design Pattern

We use State Design Pattern when an Object changes its behavior on change of its internal state. We can define the state of an object as its exact condition at any given point of time, depending on the values of its properties or attributes. The set of methods implemented by a class constitutes the behavior of its instances. Whenever there is a change in the values of its attributes, we say that the state of an object has changed.

When to use the State Design Pattern

Use the State pattern in either of the following cases:

  • When an object’s behavior depends on its state, and it must change its behavior at run-time depending on its state.
  • When operations have large, multipart conditional statements that depend on the object’s state. This state is usually represented by one or more enumerated constants. Often, several operations will contain this same conditional structure. The State Pattern puts each branch of the conditional in a separate class. This lets us treat the object’s state as an object in its own right that can vary independently from other objects.

State Design Pattern in Java

  • faces.lifecycle.LifeCycle#execute()

Iterator Design Pattern

Iterator Pattern in one of the behavioral patterns and we use it to provide a standard way to traverse through a group of Objects. We widely use the Iterator Pattern in Java Collection Framework where Iterator interface provides methods for traversing through a collection

When to use the Iterator Design Pattern

Use the Iterator pattern:

  • When you want to provide a standard way to iterate over a collection and hide the implementation logic from client program.
  • When you need to access an aggregate object’s contents without exposing its internal representation.
  • To support multiple traversals of aggregate objects.
  • To provide a uniform interface for traversing different aggregate structures (that is, to support polymorphic iteration).

Iterator Pattern in JDK

  • util.Iterator
  • util.Enumeration

Interpreter Design Pattern

Interpreter Pattern is one of the behavioral design patterns and we use it to define a grammatical representation for a language and it provides an interpreter to deal with the grammar. The best example of this pattern is java compiler that interprets the java source code into byte code that is understandable by JVM. Google Translator is also an example of interpreter pattern where the input can be in any language and we can get the interpreted output in another language.

When to use the Interpreter Design Pattern

Use the Interpreter pattern when there is a language to interpret, and you can represent statements in the language as abstract syntax trees. The Interpreter pattern works best when

  • The grammar is simple. For complex grammars, the class hierarchy for the grammar becomes large and unmanageable. Tools such as parser generators are a better alternative in such cases. They can interpret expressions without building abstract syntax trees, which can save space and possibly time also.
  • Efficiency is not a critical concern. The most efficient interpreters are usually not implemented by interpreting parse trees directly but by first translating them into another form. For example, regular expressions are often transformed into state machines. But even then, we can implement the translator with the help of Interpreter pattern, so the pattern is still applicable.

Interpreter Design Pattern in JDK

  • util.Pattern
  • text.Normalizer
  • text.Format

 Memento Design Pattern

Memento pattern is one of the behavioral design patterns. We use Memento Design Pattern when we want to save the state of an object so that we can restore it later on. Memento pattern helps to implement this in such a way that the saved state data of the object is not accessible outside of the object; this protects the integrity of saved state data.

When to use the Memento Pattern

Use the Memento Pattern in the following cases:

  • A snapshot of (some portion of) an object’s state must be saved so that it can be restored to that state later, and
  • A direct interface to obtain the state would expose implementation details and break the object’s encapsulation.

Memento Pattern in JDK

  • java.util.Date
  • java.io.Serializable

 

Structural patterns

1. Adapter Pattern

2. Composite Pattern

3. Proxy Pattern

4. Flyweight Pattern

5. Façade Pattern

6. Bridge Pattern

7. Decorator Pattern


Adapter design pattern:

Adapter design pattern is one of the structural design patterns that makes two unrelated interfaces work together. Moreover, the object that joins these unrelated interfaces is called an Adapter just like a mediator. As a real-life example, we can think of a mobile charger as an adapter because mobile battery needs 3 volts to charge, but the normal socket produces either 120V (in US) or 240V (in India). Therefore, the mobile charger works as an adapter between mobile charging socket and the wall socket.

In the adapter pattern, a wrapper class (i.e., the adapter) is used to translate requests from it to another class (i.e., the adoptee). In effect, an adapter provides particular interactions with an adoptee that are not offered directly by the adoptee.

When to use Adapter Pattern

The Adapter pattern should be used when:

  1. There is an existing class, and its interface does not match the one you need.
  2. You want to create a reusable class that co-operates with unrelated or unforeseen classes, that is, classes that don’t necessarily have compatible interfaces.
  3. There are several existing subclasses to be used, but it’s impractical to adapt their interface by sub-classing each one. An object adapter can adapt the interface of its parent class.

Adapter Pattern Example in JDK

  • util.Arrays#asList()
  • io.InputStreamReader(InputStream) (returns a Reader)
  • io.OutputStreamWriter(OutputStream) (returns a Writer)


Composite Design Pattern

The Composite Pattern allows you to compose objects into a tree structure to represent the part-whole hierarchy. It means you can create a tree of objects that is made of different parts, but that can be treated as a whole one big thing. Composite lets clients treat individual objects and compositions of objects uniformly, that’s the intent of the Composite Pattern.

In the composite pattern, a tree structure exists where identical operations can be performed on leaves and nodes. A node in a tree is a class that can have children. A node class is a ‘composite’ class. A leaf in a tree is a ‘primitive’ class that does not have children. The children of a composite can be leaves or other composites.

When to use Composite Pattern

Below is the conditions when we can use this pattern:

  1. When we want to represent part-whole hierarchies of objects.
  2. When we want clients to be able to ignore the difference between compositions of objects and individual objects. Clients will treat all objects in the composite structure uniformly.

Usage in JDK

java.awt.Container#add (Component) is a great example of Composite pattern in java and used a lot in Swing.


Proxy Design Pattern

The Proxy Design Pattern provides a surrogate or placeholder for another object to control access to it. In fact, the Proxy Pattern is used to create a representative object that controls access to another object. It may be remote, expensive to create or in need of being secured.

In the Proxy Design Pattern, a client does not directly talk to the original object, it delegates calls to the proxy object which calls the methods of the original object. Moreover, the important point is that the client does not know about the proxy.

When to use the Proxy Pattern

Proxy is applicable whenever there is a need for a more versatile or sophisticated reference to an object than a simple pointer. Here are several common situations in which the Proxy pattern is applicable:

  1. A remote proxy provides a local representative for an object in a different address space.
  2. A virtual proxy creates expensive objects on demand.
  3. A protection proxy controls access to the original object. Protection proxies are useful when objects should have different access rights.

Proxy Pattern in JDK

The following cases are examples of usage of the Proxy Pattern in the JDK.

  1. lang.reflect.Proxy
  2. rmi.* (whole package)


Flyweight Design Pattern

In the flyweight pattern, instead of creating large numbers of similar objects, we re-use objects. We can use it to reduce memory requirements and instantiation time and related costs.

Before we apply the flyweight design pattern, we need to consider the following factors:

  • If the number of Objects required in the application are huge in amount.
  • Also, if the object creation is heavy on memory and it can also be time consuming.

When to Use

Flyweight design pattern facilitates us when we need to create a lot of Objects of a class. Since every object consumes memory space, it can play a crucial role for low memory devices, such as mobile devices or embedded systems. Moreover, we can apply flyweight design pattern in order to reduce the load on memory with the help of object’s sharing.

Flyweight Pattern Example in JDK

All the wrapper classes valueOf () method uses cached objects showing use of Flyweight design pattern. The best example is Java String class String Pool implementation.


Facade Design Pattern

The Facade Design Pattern is a structural design pattern. In the facade pattern, facade classes are used to provide a single interface to set of classes. The facade simplifies a client’s interaction with a complex system by localizing the interactions into a single interface. As a result, the client can interact with a single object rather than being required to interact directly in complicated ways with the objects that make up the subsystem.

According to GoF: ‘Provide a unified interface to a set of interfaces in a subsystem. Facade Pattern defines a higher-level interface that makes the subsystem easier to use’.

When to use 

  • Facade pattern is more like a helper for client applications; it doesn’t hide subsystem interfaces from the client. Whether to use Facade or not is completely dependent on client code.
  • Facade pattern can be applied at any point of development, usually when the number of interfaces grows and system gets complex.
  • Subsystem interfaces are not aware of Facade and they shouldn’t have any reference of the Facade interface.
  • A facade pattern should be applied for similar kind of interfaces; its purpose is to provide a single interface rather than multiple interfaces that does the similar kind of jobs.
  • The subsystem may be depended with one another. In such case, facade can act as a coordinator and decouple the dependencies between the subsystems.
  • We can use the Factory pattern with Facade to provide a better interface to client systems.

Usage in Java

In javax.faces.context, ExternalContext internally uses ServletContext, HttpSession, HttpServletRequest, HttpServletResponse, etc. It allows the Faces API to be unaware of the nature of its containing application environment.


Bridge Design Pattern

When we have interface hierarchies in both interfaces as well as implementations, then the BRIDGE design pattern is used to decouple the interfaces from implementation and hiding the implementation details from the client programs.

According to the GoF bridge design pattern is: Decouple an abstraction from its implementation so that the two can vary independently.

The Bridge Pattern’s intent is to put the abstraction and implementation into two different class hierarchies so that both can be extend independently.

Adapter Pattern vs Bridge Pattern

The Adapter Design Pattern helps it two incompatible classes to work together. But the Bridge Design Pattern decouples the abstraction and implementation by creating two different hierarchies

When to Use and other points

  • Should be used when we have a need to switch implementation at runtime.
  • The client should not be impacted if there is a modification in implementation of abstraction.
  • Best used when you have multiple implementations.
  • Creates two different hierarchies. One for abstraction and another for implementation.
  • Avoids permanent binding by removing the dependency between abstraction and implementation.
  • We create a bridge that coordinates between abstraction and implementation.
  • Abstraction and implementation can be extended separately.

Usage in JDK

  • AWT (It provides an abstraction layer which maps onto the native OS the windowing support.)
  • JDBC


Decorator Design Pattern

The primary intent of the Decorator Design Pattern is to attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to sub-classing for extending functionality.

The Decorator Pattern facilitates us when we need to extend the functionality of an object dynamically without having to change the original class source or using inheritance. This is accomplished by creating an object wrapper referred to as a Decorator around the actual object.

When to use the Decorator Design Pattern

Use the Decorator pattern in the following cases:

  • To add responsibilities to individual objects dynamically and transparently, that is, without affecting other objects.
  • For responsibilities that can be withdrawn.
  • When extension by sub-classing is impractical. Sometimes a large number of independent extensions are possible and would produce an explosion of subclasses to support every combination. Or a class definition may be hidden or otherwise unavailable for sub-classing.
  • It’s easy to maintain and extend when the number of choices are more.

Usage in Java

We use the decorator pattern mostly in Java IO classes, such as FileReader, BufferedReader etc.

  • The disadvantage of decorator pattern is that it uses a lot of similar kind of objects (decorators).