Basic Design Pattern
Design patterns are reusable solutions for software development. They serve as templates that programmers can use when creating applications. They are not specific to individual programming languages, but instead are best practices or heuristics that can be applied in different programming environments.
Types of design patterns
There are about 26 Patterns currently discovered and all are classified in to 3 types
1. Creational: These patterns are designed for class instantiation. They can be either class-creation patterns or object-creational patterns.
2. Structural: These patterns are designed with regard to a class's structure and composition. The main goal of most of these patterns is to increase the functionality of the class(es) involved, without changing much of its composition.
3. Behavioral: These patterns are designed depending on how one class communicates with others
Creational - The Singleton Design Pattern
Creational design patterns describe ways to create objects using methods that are appropriate for different situations. For example, the "Singleton" pattern is used to create a basic class that will only have one instance getInstance(). A common example is a global variable defined in the source code of a program. An "Object Pool" pattern is used to create a class with a "pool" of objects that can be retrieved as needed instead of being recreated. This is often used for caching purposes.
package qa.patterns
import java.util.Objects;
public class EmployerSingleton {
//declare the instance variable
private static EmployerSingleton employerSingleton;
// private constructor, so it cannot be instantiated outside this class.
private EmployerSingleton(){}
// get the only instance of the object created.
public static EmployerSingleton getInstance(){
return Objects.isNull(employerSingleton) ? new EmployerSingleton() : employerSingleton;
}
}
;
Structural - The Decorator Design Pattern
Structural design patterns define the relationships between objects. For example, the "Private Class Data" pattern is used to limit access to a specific class. This can prevent unwanted modification of an object. The "Decorator" class, on the other hand, allows behaviors and states to be added to an object at runtime. This provides programmers with the flexibility to add as many classes to an object as needed. One example is an avatar in a video game that accumulates weapons, armor, and items throughout the game. The appropriately named "Decorator" class would provide a good framework for this process.
Consider this example, A person is running a small tea shop in his area, he is selling the tea, coffee without sugar, the current implementation is look like
First to make the Abstract Teashop class, that all the different drinks blends will inherit from:
package qa.patterns
public abstract class TeaShop {
private String description;
public TeaShop(String description) {
super();
this.description = description;
}
public String getDescription() {
return description;
}
public abstract double cost();
}
Then to add both Tea and Coffee concrete class
package qa.patterns
public class Tea extends TeaShop{
public Tea() {
super("Tea with elaichi");
}
@Override
public double cost() {
return 20.0;
}
};
package qa.patterns
public class Coffee extends TeaShop{
public Coffee() {
super("Dark roast beans");
}
@Override
public double cost() {
return 60.0;
}
};
On successful running of business, the owner starts to add addons like White Sugar, Jaggery etc...
Now the Addons class inherits from TeaShop
package qa.patterns
public abstract class Addons extends TeaShop{
protected TeaShop teaShop;
public Addons(String description, TeaShop teaShop) {
super(description);
this.teaShop = teaShop;
}
public abstract String getDescription();
};
Now the concrete implementation of Addons class
Recommended by LinkedIn
package qa.patterns
public class WhiteSugar extends Addons{
public WhiteSugar(TeaShop teaShop) {
super("White sugar", teaShop);
}
@Override
public String getDescription() {
return teaShop.getDescription()+" Sugar made from white sugarcane";
}
@Override
public double cost() {
return teaShop.cost()+ 40.00;
}
}
public class Jaggery extends Addons{
public Jaggery(TeaShop teaShop) {
super("Black jaggery", teaShop);
}
@Override
public String getDescription() {
return teaShop.getDescription()+" Pure natural black jaggery";
}
@Override
public double cost() {
return teaShop.cost()+ 80.00;
}
};
As you can see above, we can pass any subclass of Teashop to any subclass of Addons, and get the added cost as well as the updated description. And, since the Addons class is essentially of type Beverage, we can pass an Addon into another Addons. This way, we can add any number of add-ons to a specific Teahsop.
Behavioral - The Command Design Pattern
Behavioral design patterns describe the behavior of objects, such as the way they communicate with each other. One example is the "Command" pattern, which describes objects that execute commands. The "Memento" pattern records the state of an object so it can be restored to its saved stated. These two patterns can be used together to perform Undo and Redo operations in a program.
Coupling is the way that two (or more) classes that interact with each other, well, interact. The ideal scenario when these classes interact is that they do not depend heavily on each other. That’s loose coupling. So, a better definition for loose coupling would be, classes that are interconnected, making the least use of each other.
The need for this pattern arose when requests needed to be sent without consciously knowing what you are asking for or who the receiver is.
In this pattern, the invoking class is decoupled from the class that actually performs an action. The invoker class only has the callable method execute, which runs the necessary command, when the client requests it.
Let’s take a basic real-world example, ordering a meal at a fancy restaurant. As the flow goes, you give your order (command) to the waiter (invoker), who then hands it over to the chef(receiver), so you can get food. Might sound simple… but let's see it in code
What we need to implement is;
Chef the receiver
public class Chef
public void cookPasta() {
System.out.println(“Chef is cooking Chicken briyani…”);
}
public void bakeCake() {
System.out.println(“Chef is baking black forest Cake…”);
}
}
Command the interface
public interface Command
public abstract void execute();
}{
Order the concrete command
public class Order implements Command
private Chef chef;
private String food;
public Order(Chef chef, String food) {
this.chef = chef;
this.food = food;
}
@Override
public void execute() {
if (this.food.equals(“Pasta”)) {
this.chef.cookPasta();
} else {
this.chef.bakeCake();
}
}
}
Waiter the invoker
public class Waiter
private Order order;
public Waiter(Order ord) {
this.order = ord;
}
public void execute() {
this.order.execute();
}
}
You, the client
public class Client
public static void main(String[] args) {
Chef chef = new Chef();
Order order = new Order(chef, “briyani”);
Waiter waiter = new Waiter(order);
waiter.execute();
order = new Order(chef, “white cake”);
waiter = new Waiter(order);
waiter.execute();
}
}{
As you can see above, the Client makes an Order and sets the Receiver as the Chef. The Order is sent to the Waiter, who will know when to execute the Order (i.e. when to give the chef the order to cook). When the invoker is executed, the Orders’ execute method is run on the receiver.
Quick recap
In this post we went through:
I hope this was helpful.