Hey future architects! I’m back and I wanted to talk about design patterns, I know I have multiple posts about software architecture, with subjects like AWS, Domain Driven design, Hexagonal architecture that you can read on my profile, but those are more like a project side architecture and now I’ll be talking about a more functional side of the architecture, the one you’ll be using in the code, the one that will be fixing problems in the implementation of new features, or solving bugs, so, let’s start with this.
What are design patterns?
I said before that is like an architecture for a more functional side, but, what does it means? Well, these patterns are like solutions to a common problems in software design, that means, you will have a design you can customize to solve your problem but is like a template, you can’t copy and paste and wait for the problem to disappear, nope, here you’ll need to adapt the design to your particular situation.
Design patterns vs algorithms
Because the description of both are similar, people sometimes confused what is what so, let’s say, algorithms are in fact a solution for common problems, but, algorithms are a pretty detailed solution, like if you were following instructions, do this first and then do that, design patterns are more abstract, like, in order to have only one instance of a class and just one an object can be and blah blah blah, no steps, no instructions, just an idea of how you could it, did I explain myself correctly? I hope so, if I don’t, maybe with the examples and descriptions you’ll see next you’ll understand better what I said.
Classifications
Like everything in life, there are classifications, it’s not the same a car than an bus, they’re both vehicles, but they’re not the same, the same thing applies for designs patterns, there are three classifications:
- Creational patterns: Provide object creation mechanisms that increase flexibility and reuse of existing code.
- Structural patterns: Explain how to assemble objects and classes into a larger structures while keeping these structures flexible and efficient.
- Behavioral patterns: Take care of effective communication and the assignment of responsibilities between objects.
To not make this post a monster, I’ll take about creational patterns and I’ll make another posts for the others classifications.
Creational Patterns
Factory Method
This pattern just defines that you can creates an instance from a superclass but letting subclasses decides the type of object will be created, confused right? Imagine you are the owner of Uber, and you have your application and then, some companies for social events calls you, they want to add their services to your app, so, now, you don’t have only cars available, maybe you have limousines or buses or any other type, what will you do? The answer, implement this pattern.
// Superclass vehicle
public abstract class Vehicle {
protected float price;
public Vehicle() {}
abstract void calculatePrice();
}
// Subclass Car
public class Car extends Vehicle {
public Car() {this.super();}
@Override
public void calculatePrice() {
this.price += 300;
}
}
// Subclass Limusine
public class Limusine extends Vehicle {
public Limusine() {this.super();}
@Override
public void calculatePrice() {
this.price += 600;
}
}
// The vehicle creator or factory
public class VehicleFactory {
public VehicleFactory() {}
// Using an imaginary enum with two values CAR and LIMUSINE
public Vehicle getVehicle(VehicleTypes vtypes) {
if(vtypes === VehicleTypes.CAR) {
return new Car();
} else if(vtypes === VehicleTypes.LIMUSINE) {
return new Limusine();
}
return null;
}
}
Abstract Factory
It is a creational design pattern that lets you produce families of related objects without specifying their concrete classes, haha, all of them have descriptions that no one can understand at first try, imagine this, you sell coffee and tea, both can be in three sizes and three different glasses depending on the size, solution? Abstract factory suggest to have classes for every type of coffee and tea, SmallCoffee, BigCoffee, HugeCoffee, SmallTea, BigTea, HugeTea and have a creator each type:
// Example for give you an idea how it works it can be improve like
// avoiding duplicates and things like that
// Coffee entity
public class Coffee {
private int ounces;
private CoffeeCupType presentation;
public Coffee(int ounces, CoffeeCupType presentation) {
this.ounces = ounces;
this.presentation = presentation;
}
}
// Tea entity
public class Tea {
private int ounces;
private TeaCupType presentation;
public Tea(int ounces, TeaCupType presentation) {
this.ounces = ounces;
this.presentation = presentation;
}
}
// Example for give you an idea how it works it can be improve like
// avoiding duplicates and things like that
public class DrinkFactory {
public DrinkFactory() {}
// Imaginary size enum with the values SMALL, BIG and HUGE
public Coffee getCoffee(Size size) {
CoffeeFactory coffeeFactory = new CoffeeFactory();
if(size === Size.SMALL) {
return coffeeFactory.small();
} else if(size === Size.BIG) {
return coffeeFactory.big();
} else if(size === Size.HUGE) {
return coffeeFactory.huge();
}
return null;
}
public Tea getTea(Size size) {
TeaFactory teaFactory = new TeaFactory();
if(size === Size.small()) {
return teaFactory.small();
} else if(size === Size.BIG) {
return teaFactory.big();
} else if(size === Size.HUGE) {
return teaFactory.huge();
}
return null;
}
}
// Example for give you an idea how it works it can be improve like
// avoiding duplicates and things like that
public class CoffeeFactory {
public CoffeeFactory() {}
public Coffee small() { return new Coffee(11, CoffeeCupType.BRONZE); }
public Coffee big() { return new Coffee(14, CoffeeCupType.SILVER); }
public Coffee big() { return new Coffee(16, CoffeeCupType.DIAMOND); }
}
public class TeaFactory {
public TeaFactory() {}
public Tea small() { return new Tea(11, TeaCupType.BRONZE); }
public Tea big() { return new Tea(14, TeaCupType.SILVER); }
public Tea big() { return new Tea(16, TeaCupType.DIAMOND); }
}
Like I said before, design patterns are not algorithms, they don’t have a step by step so, it suggested having an entity for every type, SmallCoffee, BigCoffee, etc, but my example, it’s pretty simple just to give you an idea and I won’t spend much time to create everything and optimizing everything, so, I customized it to solve the problem on my example, see?
Builder
It is a creational design pattern that lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code. Imagine you have a software that creates your perfect house, and every house has a lot of different variations like, windows, yards, pools, garage, etc, I know that maybe you’re thinking I’ll solve it having a huge constructor with a lot of parameters and then… But that’s an awful idea, instead, watch this:
// House Entity
public class House {
private int windows;
private int rooms;
private PoolSize poolSize;
public House() {}
// Getters and setters
...
}
public enum PoolSize {
NONE,
SMALL
}
public class Director {
public Director() {}
public void buildSimpleHouse(SimpleHouseBuilder builder) {
builder.buildWindows();
builder.buildRooms();
builder.buildPoolSize();
}
public void buildLuxuryHouse(LuxuryHouseBuilder builder) {
builder.buildWindows();
builder.buildRooms();
builder.buildPoolSize();
}
}
// Builder for a simple house
public class SimpleHouseBuilder {
private House house;
public SimpleHouseBuilder(House house) { this.house = house; }
public void buildWindows() {
this.house.windows = 3;
}
public void buildRooms() {
this.house.rooms = 1;
}
public void buildPoolSize() {
this.house.poolSize = PoolSize.NONE;
}
public House getHouse() {
return this.house;
}
}
// Builder for a luxury house
public class LuxuryHouseBuilder {
private House house;
public LuxuryHouseBuilder(House house) { this.house = house; }
public void buildWindows() {
this.house.windows = 6;
}
public void buildRooms() {
this.house.rooms = 3;
}
public void buildPoolSize() {
this.house.poolSize = PoolSize.SMALL;
}
public House getHouse() {
return this.house;
}
}
public class Main {
public static void main(String args[]) {
House house = new House();
Director director = new Director();
SimpleHouseBuilder builder = new SimpleHouseBuilder();
director.buildSimpleHouse(builder);
house = builder.getHouse();
}
}
You can see multiple ways of doing this, remember, it suggest you customize, maybe you’ll see example with a syntax like
House house = new House(windows, rooms)
.SimpleHouseBuilder(bathrooms, color).setPoolSize(PoolSize.NONE).build();
Prototypes
There’s not much description here, basically it’s a way of copy an object, having an exact copy of the actual object, let’s do it:
public abstract class Car {
protected float km;
protected CarColor color;
public Car(CarColor color) {
this.color = color;
}
public void setKm(float km) {
this.km = km;
}
abstract Car clone();
}
// Truck entity
public class Truck extends Car {
public Truck(CarColor color) {
this.super(color);
}
public Car clone() {
Truck truck = new Truck(this.color);
truck.setKm(this.km);
return truck;
}
}
// Sedan entity
public class Sedan extends Car {
public Sedan(CarColor color) {
this.super(color);
}
public Car clone() {
Sedan sedan = new Sedan(this.color);
sedan.setKm(this.km);
return sedan;
}
}
public class Main {
public static void main(String... args) {
Truck truck = new Truck(CarColor.RED);
truck.setKm(10000);
Truck anotherTruck = (Truck) truck.clone();
}
}
Singleton
Pattern that lets you ensure that a class has only one instance, while providing a global access point to this instance, and the two common steps to achieve that are:
- Make the default constructor private, to prevent other objects from using the new operator with the Singleton class.
- Create a static creation method that acts as a constructor. Under the hood, this method calls the private constructor to create an object and saves it in a static field. All following calls to this method return the cached object.
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if(singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
public class Main {
public static void main(String... args) {
Singleton single = Singleton.getInstance();
}
}
Alright, I covered all the creational design patterns, maybe it’s a lot for you to remember but in the practice, you’ll be able to use them without looking on internet how to do it, you’ll remember the logic behind the pattern and automatically you’ll think a way to customize it for your current situation. That’s everything from my side, I hope you enjoyed reading this post, a more coding post haha and see you on the next one!