Design Patterns
Problems can occur so often that it is given a name and known solution.
A design pattern is a proven, reusable solution to a commonly occurring problem.
Writing good software means recognizing patterns that can help us write code that is easy to maintain in the long run. It also makes it easy for us to communicate with other software developers.
Well-Known Patterns
DAO Design Pattern is used to decouple the persistence mechanism and logic into a separate layer.
- Adding persistence to an application means storing data so that data persists beyond the lifetime of the application execution.
- Popular when designing systems to use with databases.
Introduce an abstraction between the database (persistence) and the rest of the application.
E.g., The BusinessObject uses DataAccessObject, which encapsulates the DataSource.
Dependency Injection Pattern A dependency is another object which your class needs to function.
E.g., A Model class that fetches data from Database class has a dependency on the Database object.
Injecting dependency means the "dependency" is pushed to the class from the outside; all that means is that you should not instantiate the dependencies (like through using the new operator inside of a class). Instead, take it as a constructor parameter (or via a setter method):
public class Model {
private Database aDatabase;
public Model(Database db) {
aDatabase = db;
}
}
Dependency Injection allows the entities in our application to be loosely coupled to one another. The object that is injected (passed into the client code) is called Service. The code that passes in (injects) the service (dependency) is called Injector.
- The intent behind dependency injection is to separate the concerns of construction and use of objects. Thus, increasing readability and code reuse.
MVC Pattern MVC is an architectural pattern that is commonly used for creating web applications. The pattern promotes separating application's concerns and results in a system design comprised of entities with clear responsibilities.
Gang of Four is a book that discusses patterns and solutions.
Composite
A composite is a structural design pattern that lets you compose objects into tree structures to represent whole-part hierarchies. Composites lets clients treat a group of objects the same way as a single instance of the same type of object.
The composite design pattern provides a mean of grouping together several objects of type T such that the grouped object (i.e. composite object) is also of type T.
Diagram explained:
-
Component:
- declares the abstraction for objects in the composition
- could optionally implement default behavior common to all subclasses
-
Composite:
- represents an aggregate object (collection of components)
- usually has methods to add/remove component
- implements the operations of
Componentsimply by calling the same operation for each of its constituent components.
-
Concrete Component (aka Leaf)
- defines behavior for primitive objects in the composition
When to use?
- when you need to manipulate a hierarchical collection of "primitive" and "aggregate" objects
- Need to process (treat) primitive objects the same way as aggregate objects
- e.g., file/folder hierarchies, organizational charts, table of contents, parsing structured documents like XML/HTML, etc.
Benefits
Clients will only need to interact with the Component interface; they would be unaware if they're dealing with a composite or a leaf object. This hides the complexity of the data structure from the outside world.
Adapter
Adapter is a structural design pattern that allows the interface of an existing class to be used as another interface. It allows objects with incompatible interfaces to collaborate.
When to use? Use the Adapter when you want to use some existing class, but its interface isn't compatible with the rest of your code
Benefits You don't need to change the existing class or interface. By introducing a new class, which acts as an adapter between the interface and the existing class, you reuse your existing code without changing it.
Example:
- We have a legacy
Rectanglethat usesx,y,w,hparameters. - However, the client code uses a
Pointclass and wants to pass in two pointsp1andp2. - This incongruity an be fixed by adding a level of indirection (an
Adapterobject) that allows the client code to interact with a newRectangleclass - The code for the new
Rectangleclass will look something like:
public class Rectangle implements Shape {
private LegacyRectangle lr;
@Override
public void display(Point p1, Point p2) {
int w = p2.getX() - p1.getX();
int h = p1.getY() - p2.getY();
lr.displaya(p1.getX(), p2.getY(), w, h);
}
}
Note: this pattern follows the dependency inversion principle.
Factory
Factory is a creational design pattern that allows you to replace direct object construction calls (using the new operator) with call to a special factory method.
When to use this pattern Use the factory pattern when you want to provide the users of your library or framework with a way to extend its internal components, allowing your clients to decouple their application from concrete implementations of your framework.
Benefits Factory pattern removes the instantiation of actual implementation classes from client code, thus making it more robust, less coupled, and easy to extend.
- Imagine if properly instantiating an object from a class depends on creating many other objects first, or you have configure all the settings. The client code shouldn't deal with it because the user might not know how to do it.
The factory serves as a knowledge center for producing objects. When you move all of those into one separate place, just in case the client must instantiate multiple times. Remember, duplicate code is a code smell!
Example:
- Seen in the JDBC API;
DriverManager.getConnectionis a factory method
import java.sql.*
public class Main{
public static void main(String[] args) throws SQLException {
final String URI = "jdbcLsqlite:./Strore.db";
Connection conn = DriverManager.getConnection(URI);
// do something with the connection
conn.close()l
}
}
- the
DriverManageris responsible for creating theConnectionobjects instead of usingnewwithConnection. Under the hood,DriverManagerfinds the SQLite driver (fromURI) and calls theconnect()method of the SQLite driver which returns the concrete implementation of theConnectionclass (that is, the subclass of theConnectionclass that is specialized to work with SQLite). This eliminates the need for us to specify the concrete implementations ofConnectionin our code.
When you use the factory pattern, the client code is essentially asking the factory to make an implementation. The client code does not decide which implementation to make and therefore is not coupled to it.
Singleton
Singleton is a creational design pattern that lets you ensure that a class has only one instance, while providing a global access point to this instance.
When to use this pattern Useful when you should only have one instance of the class.
- E.g., working with a database/text file. It should be one universal source of information to avoid inconsistencies.
- Other examples include a single logging object or hardware drivers' objects
Singleton Pattern is also resource friendly. By creating only one instance, we do not waste memory for a new object when we don't need one.
It provides more flexibility in the sense that the class itself controls the instantiation process, so the class instantiation process can be adjusted in one place.
Benefits Make sure that a class has only a single instance. Gain a global access point to that instance.
- Safer than global variables (since any code can overwrite the contents of those variables and crash the app). Singleton lets you safely access and protects it from being overwritten
Implementing this pattern
public final class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
- The default constructor is private to prevent other objects from using the
newoperator with the Singleton class. - Create a
staticcreation method that acts as a constructor.
Example:
This design pattern is used in core java classes like java.lang.Runtime.
- every Java application has a single instance of the class
Runtimethat allows the application to interface with the environment in which the application is running. The current runtime is obtained form thegetRuntimemethod.
Iterator
Iterator is a behavioral design pattern that lets you traverse elements of a collection without exposing its underlying representation.
- In Java Collection Framework,
Iteratorinterface methods for traversing through collections.
In Java, implement by implementing the Iterable and Iterator interfaces.
- Typically the iterator algorithm is implemented in a separate class. This separation is following the Single Responsibility Principle
- The use of
Iterablealso is in line with the Interface Segregation Principle. The use ofIteratorinterface is in accordance to the Open/Close principle as you can implement new types of iterators and pass them to existing code without breaking anything. - Sometimes, the class is placed as an inner class inside the data structure class itself.
When to use? Use when your collection has a complex data structure under the hood, but you want to hide its complexity from clients for convenience or security reasons.
Benefits hides the actual implementation of traversal through the collection and client programs only need to use the iterator methods.
Example:
for (String s : set) {...}
is syntax sugar for
Iterator<String> it = set.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
Observer
An observer is a behavioral design pattern that lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they're observing.
- Also called the publish-subscribe pattern. Allows you to get notified whenever there is a change in state of an object you are interested in.
- The observer is the subscriber, and the object that offers its interesting state to its observer/subscriber is the subject aka publisher.
Example: The pattern is used in GUI components through the Action/Event Listener. Each GUI component is a subject where different events (e.g., a click) would send a notification to an observer class (event listener) which would perform different actions based on the occurence of the event.
In Java, the Observer pattern can be implemented through the Observerable class and Observer interface.
- The subject extends
Observable - Observers implement
Observer- In line with the Interface Segregation Principle and Open/Closed principle; you can add multiple new types of observes and add them to subject without breaking the application.
Note: The Observerable class and the Observer interface have been deprecated in Java. Use java.beans for a notification model.
When to use? Use the Observer pattern when changes to the state of one object requires changing other objects, and the actual set of objects is unknown before hand or changes dynamically.
Benefits
Observers are loosely coupled to subjects since the Subject knows nothing about them, other than that they implement the Observer interface.
Linked from Object-Oriented Software Engineering