A software design pattern is a general, reusable solution to a commonly occurring problem within a given context in software design. It is not a finished design that can be transformed directly into source or machine code. Rather, it is a description or template for how to solve a problem that can be used in many different situations. Design patterns are formalized best practices that the programmer can use to solve common problems when designing an application or system.
Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice. --- Christopher Alexander, 1977
If someone asks why we shoud use patterns, these are the possible answers:
- Reuse tried, proven solutions
- Provides a rapid start
- Avoids unanticipated things
- No need to reinvent the wheel
- Establish common terminology
- Design patterns provide a common point of reference
- Easier to say, We could use Strategy here.
- Provide a higher level prospective
- Frees us from dealing with the details too early
- Most design patterns make software more modifiable, less brittle
- we are using time tested solutions
- Using design patterns makes software systems easier to change (more maintainable)
- Helps increase the understanding of basic object-oriented design principles
- encapsulation, inheritance, interfaces, polymorphism
According to GoF (gang of four), the writers of Design Patterns: Elements of Reusable Object-Oriented Software, there are these pattern categories:
Used to create objects. They decouple the construction of an object from its representation. Object creation is encapsulated and swapped out to keep the context of object creation independent of the concrete implementation, according to the rule: "Program on the interface, not on the implementation!
- Abstract factory
- Factory method
Facilitate the design of software with ready-made templates for relationships between classes.
Model complex behavior of the software and thus increase the flexibility of the software regarding its behavior.
- Chain of Responsibility
- Template method
In the following sections we will get to know some of these patterns:
Problem: Exactly one instance of a class is allowed. Objects need a global and single point of access.
Solution: Define a static method of the class that returns the singleton:
getInstance(). Singleton supports global visibility or a single access point to a single instance.
Problem: Who should be responsible for creating objects when there are special considerations such as complex creation logic, a desire to separate creation responsibilities for better cohesion, etc.?
Solution: Create a Pure Fabrication object called a Factory that handles the creation. The benefits of this approach are:
- Separate the responsibility of complex creation into cohesive helper objects
- Can provide object caching (e.g. having only one random number generator)
Problem: How to resolve incompatible interfaces, or how to provide a stable interface to similar components with different interfaces.
Solution: Convert the original interface of a component into another interface, through an intermediate adapter object.
Note: the Adapter pattern is an application of Polymorphism
Problem: you want to add dynamically useful features to an existing object.
Solution: an object (decorator) that modifies behavior of, or adds features to, another object.
- decorator must maintain the common interface of the object it wraps up
- used so that we can add features to an existing simple object without needing to disrupt the interface that client code expects when using the simple object
- the object being "decorated" usually does not explicitly know about the decorator
Problem: How to treat a group or composition structure of objects the same way (polymorphically) as a non-composite (atomic) object?
Solution: Define classes for composite and atomic objects so that they implement the same interface. The key feature: the composite object contains a list of inner objects and both the composite object and the inner objects implement the same interface.
Problem: A common unified interface to a disparate set of implementations or interfaces – such as within a subsystem – is required. There may be undesirable coupling to many things in the subsystem, or the implementation of the subsystem may change. What to do?
Solution: Define a single point of contact to the subsystem – a façade that wraps the subsystem. This façade object presents a single unified interface and is responsible for collaborating with the subsystem components:
- A façade object serves as a “front-end” object that is the single point of entry for the services of a subsystem.
- Façade provides Protected Variation from changes in the implementation of the subsystem
- Façade is usually accessed via Singleton
- Similar to Adapter but Adapter applies to varying interfaces not hiding implementation of subsystem
Problem: Different kinds of subscriber objects are interested in state changes or events of a publisher object and want to react in their own unique way when the publisher generates an event. The publisher wants to maintain low coupling to the subscribers.
Solution: Define a subscriber or listener interface. Subscribers implement the interface. The publisher can dynamically register subscribers who are interested in an event and notify them when an event occurs. Observer Pattern is also known as Publish-Subscribe Pattern.
Problem: You want to encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
Solution: represent units of work as Command objects:
- Interface of a Command object can be a simple execute() method
- Extra methods can support undo and redo
- Commands can be persistent and globally accessible, just like normal objects
Problem: How to design for varying but related algorithms or policies? How to design for the ability to change these algorithms or policies?
Solution: Define each algorithm/strategy in a separate class with a common interface.
- Strategy is based on Polymorphism and provides Protected Variation with respect to changing algorithms
- Strategies are often/usually created by a Factory
There is an interesting list of some good examples of these Design Patterns in Java's core libraries here: examples-of-gof-design-patterns-in-javas-core-libraries
- Using SingletonPattern.java as a starting point, create a class that manages a fixed number of its own objects. Assume the objects are database connections and you only have a license to use a fixed quantity of these at any one time.
- The java.util.Map has no way to automatically load a two-dimensional array of objects into a Map as key-value pairs. Create an adapter class that does this.
- Suppose you have a Bird class with fly() and makeSound() methods
- And also a ToyDuck class with squeak() method.
- Let’s assume that you are short on ToyDuck objects and you would like to use Bird objects in their place.
- Birds have some similar functionality but implement a different interface, so we can’t use them directly.
- So we will use adapter pattern. Here our client would be ToyDuck and adaptee would be Bird.
- Implement the code for this scenario