Design patterns. Why? and how?
Introduction
Object oriented programming was invented to solve many software problems, it made it possible to have reusable and maintainable components. This came so sudden and people started using it without following any standards or rules to prevent them from falling into its pitfalls.
Let’s say you’re developing a game with a team and the code base is really huge. You were responsible for implementing a feature of a bot in the game, and you placed it in the wrong spot increasing the coupling to a dangerous level. At first everything is working fine but then the game needed an update in this exact feature. It have already too many dependencies so probably you’re gonna fail some tests and maybe introduce a bug.
Here we have the design patterns in the rescue. They give you certain rules that would have prevented this domino effect.
In this blog post, we’re going to get to know some of the design patterns with illustrated code samples.
You’ll note that in any class in the code samples bellow we have all the setters private so that the class would be immutable and we also use the getters inside its class so that you would prevent the case of having a really huge class using this.field and suddenly the rule of getting this field changes. So instead of applying this new rule in every occurrence of this.field you’ll just change this.getField().
Types of design patterns
- Structural patterns: They are here basically to organize relations between objects. They give you the best and simplest way to identify relationships between entities.
The patterns that are under this category are (we’re going to see those in bold): Proxy, Facade, Composite, Adapter, Decorator, Bridge, Flyweight. - Behavioural patterns: They are used to control interactions between objects by organizing and distributing them the right way. These models attempt to divide the responsibilities between each class.
The patterns that are under this category are (we’re going to see those in bold in part 2): Observer, State, Strategy, Iterator, Visitor, Command, Chain of responsibility, Interpreter, Mediator, Memento, Template Method, Callback. - Creational patterns: They are used to solve problems of object creation. These models are current in the design of a class that have the responsibility of creating many objects.
The patterns that are under this category are (we’re going to see those in bold in part 3): Factory, Abstract factory, Builder, Singleton, Prototype.
Definition and code samples
Proxy
A proxy is a class that control the access to the original class by providing some of its features. Its does not give you the control on the original object until you request it, so, you won’t get an object that will consume too much memory to perform small actions.
Its works like most of the images viewing system on modern websites. First you see only a thumbnail, that is a small version of the image with a much smaller size than the original, And finally when you click it you get the original image with the original size.
In this pattern, we have the subject (a common interface), the real subject, the proxy (which controls the access to the real object)
Let’s implement the example of the images using the proxy pattern.
interface Image {
public void displayImage();
}
class HighResolutionImage implements Image{
public HighResolutionImage() {
this.loadImage();
}
@Override
public void displayImage() {
System.out.println("Real image displayed here!");
}
private void loadImage(){
System.out.println("Real image loaded!");
}
}
class Proxy implements Image{
private HighResolutionImage realImage;
private void setRealImage(HighResolutionImage realImage) {
this.realImage = realImage;
}
public HighResolutionImage getRealImage() {
return realImage;
}
@Override
public void displayImage() {
if(this.getRealImage() == null){
this.setRealImage(new HighResolutionImage());
}
this.getRealImage().displayImage();
}
}
class demo {
public static void main(String[] args){
Proxy imageProxy = new Proxy();
//untill now we have the proxy on the image but it's not loaded so maybe getting just a thumbail or a soft preview
imageProxy.displayImage();
//now that we called the display method we have the full resolution loaded
imageProxy.displayImage();
//the real image is displayed without reloading
}
}
Facade
A Facade is a class that that regroup a number of interfaces of certain objects in one place so that the client won’t worry about the other low level interfaces and just make the request to a unique interface that can handle all the complications.
Let’s take for example an RPG game that have multiple enemies, each one have its own way to scream :
interface Ennemi {
public void scream();
}
class Soldier implements Ennemi {
@Override
public void scream() {
System.out.println("Uh-ha!");
}
}
class Ogre implements Ennemi {
@Override
public void scream() {
System.out.println("Aaarg!");
}
}
class Minion implements Ennemi {
@Override
public void scream() {
System.out.println("a..");
}
}
class EnnemiFacade {
private Ennemi soldier;
private Ennemi ogre;
private Ennemi minion;
public EnnemiFacade(){
soldier = new Soldier();
ogre = new Ogre();
minion = new Minion();
}
public void makeASoldierScream() {
soldier.scream();
}
public void makeAnOgreScream() {
ogre.scream();
}
public void makeAMinionScream() {
minion.scream();
}
}
class facade demo {
public static void main(String[] args) {
EnnemiFacade ennemiFacade = new EnnemiFacade();
ennemiFacade.makeAnOgreScream();
//you'll hear the ogre scream without knowing the low level classes that are involved
}
}
Composite
This pattern is very useful when we’re talking about a composition of objects with a depth that is unknown and can vary.
In this patterns you will work with your objects as if they are in a tree. So you’ll have to know which objects can be leafs (Children that have no more children) and which be parents(can have parents also) knowing that each leaf can have no more than one parent.
You would understand this easier with an example. Let say we have more boxes than may contain dimes in it. Each has a price, so you could tell that the box’s price is the the sum of its dimes price.
interface Component {
public int getTotalPrice();
}
class Box implements Component{
private List<Component> components = new ArrayList<>();
public List<Component> getCompenents(){
return this.components;
}
public void addComponent(Component component){
this.getCompenents().add(component);
}
@Override
public int getPrice() {
int globalPrice = 0;
System.out.println("This box contain the following elements :");
for(Comonent co : this.getCompenents()){
globalPrice += co.getTotalPrice();
}
System.out.println("The global price of the box is " + globalPrice + "$");
return globalPrice;
}
}
class Dime implements Component{
private int price;
public int getPrice(){
return price;
}
private int setPrice(int price){
this.price = price;
}
public Dime(){
this.setPrice(100);
}
@Override
public int getTotalPrice() {
System.out.println("The dime is worth " + this.getPrice() + "$");
return this.getPrice();
}
}
class demo {
public static void main(String[] args){
Component blueDime = new Dime(100);
Component secondBlueDime = new Dime(120);
Component blueDimesBox = new Box();
blueDimesBox.addComponent(blueDime);
blueDimesBox.addComponent(secondBlueDime);
Component yellowDime = new Dime(200);
Component secondYellowDime = new Dime(220);
Component yellowDimesBox = new Box();
yellowDimesBox.addComponent(blueDime);
yellowDimesBox.addComponent(secondBlueDime);
Component bigBox new Box();
bigBox.addComponent(blueDimesBox);
bigBox.addComponent(yellowDimesBox);
//totalBoxPrice is the total price of the big box that
//contains the two boxes that each contain dimes.
//bigbox.getTotalPrice() will get recursively the price
//from the composite (the box) elements untill it
//gets to the leaf (the dime ) that cannot contains
//any other element
int totalBoxPrice = bigbox.getTotalPrice();
//we'll get in the output all the prices of the dimes
//as the method itterate the boxes recursively
}
}
To see the use of this pattern even further. Let say we got new elements in game: bags and rings. The bag and the box can contain each other and the bag cannot contain dimes. Finally we want to calculate the total price of an element.
For example we have a bag bigBag that contain the box bigBox from the last example, in addition to a ring goldenRing.
It’s clear that the bag, is a component and a composite because it can contain other bags and boxes, on the other hand, ring is clearly a component too but, a leaf at the same time because it cannot contain anything.
So here’s the Bag class:
class Bag implements Component{
private List<Component> components = new ArrayList<>();
public List<Component> getCompenents(){
return this.components;
}
public void addComponent(Component component){
if(component instanceof Dime){
System.out.println("A dime cannot be added to a bag");
} else {
this.getCompenents().add(component);
}
}
@Override
public int getPrice() {
int globalPrice = 0;
System.out.println("This bag contain the following elements :");
for(Comonent co : this.getCompenents()){
globalPrice += co.getTotalPrice();
}
System.out.println("The global price of the box is " + globalPrice + "$");
return globalPrice;
}
}
And here’s the Ring class :
class Ring implements Component{
private int price;
public int getPrice(){
return price;
}
private int setPrice(int price){
this.price = price;
}
public Ring(){
this.setPrice(300);
}
@Override
public int getTotalPrice() {
System.out.println("The ring is worth " + this.getPrice() + "$");
return this.getPrice();
}
}
Now everything is done! And there you have it. You can add as many new elements as you wish without having to change the existing code. It’s that easy.
Adapter
Let’s say you have an interface that you cannot change for the reason that it is a client interface or it have some other dependencies that can be affected by that change and you still need to use it. So for us to work with this interface along with the other clients, we are going to make an adapter that allows us to adapt our commands to some that can be understood by this interface.
This pattern is relatively easy to use. Let’s say we have a component that only understands the CLICKEVENT and ARROWRIGHTEVENT. And we can only provide TOUCHEVENT and SWIPERIGHTEVENT. To implement this with the minimum impact on the existing code you’ll just to use an adapter.
interface OldDevice {
public Event click();
public Event arrowRight();
}
class NewDevice {
public Event touch(){
return Events.TOUCHEVENT;
}
public Event swipeRight(){
return Events.SWIPERIGHTEVENT;
}
}
class OldToNewDeviceAdapter implements OldDevice{
private NewDevice newDevice;
private void setNewDevice(NewDevice newDevice) {
this.newDevice = newDevice;
}
public NewDevice getNewDevice(){
return this.newDevice;
}
public OldToNewDeviceAdapter(NewDevice newDevice){
this.setNewDevice(newDevice);
}
@Override
public Event click(){
return this.getNewDevice().touch();
}
public Event arrowRight(){
return this.getNewDevice().swipeRight();
}
}
Now, you can use your new device as if it's the old one by using the adapter
class demo {
public static void main(String[] args){
OldDevice oldDevice = new OldToNewDeviceAdapter(new NewDevice());
oldDevice.click(); //will return TOUCHEVENT
}
}
For the next patterns we are just gonna see a brief definition each and we’ll gonna cover them more in further parts of this blog.
Observer
This pattern falls under the behavioural category and comes in handy when you need a group of classes to be notified of any change of the state of another group of objects.
Strategy
You can use this pattern when the behaviour of an object changes depending on its state. This seems useless in a small scale because you would say “let’s just use the object the right way because he has the knowledge of its state”. In a large base of code when the state changes frequently, you’ll often make mistakes of reading its current state or knowing the right behaviour of each state. This pattern let you always make the same command without worrying about the state and still have the right behaviour.
State
It’s almost exactly the same as the strategy pattern but the only difference is when the whole behaviour of the object in concern changes completely and not just some of its features.
Iterator
You need to use this pattern when you need to iterate through a list of objects to perform operations on them knowing that you’ll have more operations to perform in a loop in the future. This pattern come in handy in this kind of situations because it allows you to add more operations without touching the existing code.
Factory
This is one of the most used pattern, and you have already used it multiple times unknowingly. It comes under creational patterns and with it, clients of the factory can create an object without exposing the complication behind it by using one simple interface.
Builder
You can use this pattern when it is too difficult to create a final complete object, by building it step by step
Singleton
This pattern is the easiest to identify when to use and how to implement. It assure you that there is one and only one instance of a class and provides you this instance with a unique method.
Conclusion
All we can say is that there is no one way of implementing and doing things. You can always agree or disagree with your teammates on using a pattern in a certain situation depending on how you see it, but the more you’re used them and using them in real life situations, the more you’re getting the design pattern to become an exact science.