Mastering Design Patterns: Introduction

Mastering Design Patterns: Introduction

An Introduction to Design Patterns

Are you dreaming of coding super sturdy, easy-to-maintain, and slick code that won't become obsolete next year? Well, it's not just a dream! It can be your reality, thanks to something called Design Patterns. But you might be asking yourself, "What on earth are Design Patterns, and why do they matter?" Don't worry, we've got you covered. In this article, we will help to clear up any confusion about this key player in the coding world. We'll be chatting about what Design Patterns are, why they're pretty important, and we'll throw in some cool examples from the popular book, "Head First Design Patterns" by Eric Freeman, Ph.D. , Elisabeth Robson , Bert Bates, and Kathy Sierra. So, let's dive in and discover the magic of Design Patterns together!

What are Design Patterns?

Design Patterns are reusable solutions to common problems that occur in software design. They are best practices, established over time, and provide templates designed to help solve recurring design problems, thus making our code more flexible, reusable, and maintainable. Design Patterns aren't complete designs that can be transformed directly into source or machine code. Instead, they are descriptions or templates for how to solve a problem that can be applied in many different situations.

The Gang of Four (GoF), a group of four authors, first brought Design Patterns to the limelight with their seminal book "Design Patterns: Elements of Reusable Object-Oriented Software". For beginners, though, "Head First Design Patterns" provides a more accessible introduction. This book categorizes design patterns into three groups:

  1. Creational Patterns: Deal with object creation mechanisms, trying to create objects in a manner suitable to the situation.
  2. Structural Patterns: Deal with object composition, ensuring the classes and objects are composed in a manner that ensures efficiency.
  3. Behavioural Patterns: Concern themselves with communication between objects, how they interact, and distribute work.

Why are Design Patterns Used?

Design Patterns are vital in software development for several reasons:

  1. Code Reusability and Maintainability: Design patterns provide tested, proven development paradigms that can improve developer productivity and make software more maintainable.
  2. Expressiveness: With design patterns, developers can communicate using well-known, understood names for software interactions. Common design patterns can be improved over time, producing code that is easier to read and understand.
  3. Flexibility: Design patterns make a software system more adaptable, stable, and flexible, as they provide solutions to general problems.
  4. Avoidance of Superfluous Solutions: They prevent the development of non-optimal solutions that can increase complexity.

Design Patterns taken from "Head First Design Patterns"

The Design Patterns we are going to talk about aren't the only ones out there. The list is much longer! However, the ones that are listed below, are most commonly used.

  1. Strategy Pattern (Behavioural): This pattern enables a strategy, or algorithm, to be selected at runtime. For example, consider a sorting algorithm. A program could use different sorting strategies (quick sort, merge sort, bubble sort) based on the nature of the data or user preference.
  2. Observer Pattern (Behavioral): This pattern is used when there is a one-to-many relationship between objects such as if one object is modified, its dependent objects are to be notified automatically. An example could be a newsletter system: once a new article is published (modification), subscribers receive notifications.
  3. Decorator Pattern (Structural): This pattern is used to add new functionality to an existing object, without altering its structure. For example, in a coffee shop app, the base class could be a simple coffee, and decorators could be add-ons like cream, sugar, or milk.
  4. Factory Method Pattern (Creational): This pattern provides an interface for creating objects in a superclass, but lets subclasses alter the type of objects that will be created. For instance, in a game application, a factory method could be used to create different types of enemy characters based on game levels.
  5. Singleton Pattern (Creational): This pattern ensures a class has only a single instance and provides a global point of access to it. A classic example is a logging class or a database connection. In these scenarios, it's useful to have only one instance managing the logging or the database connection, rather than multiple instances causing potential conflicts or resource overuse.
  6. Command Pattern (Behavioral): This pattern is used to encapsulate a request as an object, thereby letting you parameterize other objects with queues, requests, and operations. For example, in a home automation system, a remote control is a command object, each button press is a request, and the execution occurs in the receiver object - the device performing the action.
  7. Adapter and Facade Patterns (Structural): The adapter pattern converts the interface of a class into another interface that clients expect. It lets classes work together that couldn’t otherwise because of incompatible interfaces. On the other hand, the facade pattern provides a simplified interface to a complex subsystem. An example of the facade pattern is a power button on a computer - behind that simple button press is a complex series of actions, but all the user has to do is press the power button.
  8. Template Method Pattern (Behavioural): This pattern defines the steps of an algorithm and allows the redefinition of certain steps. For instance, in an application that generates reports, the application could define the steps for generating the report in a method, and individual steps could be redefined by subclasses to provide custom formatting or data manipulation.
  9. Iterator and Composite Patterns (Behavioural/Structural): The iterator pattern provides a way to access the elements of an aggregate object without exposing its underlying representation. The composite pattern allows you to compose objects into tree structures to represent part-whole hierarchies. They often work together. For example, a menu system in a restaurant could be designed with the composite pattern, and the iterator pattern could be used to traverse through the items.
  10. State Pattern (Behavioral): This pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class. In a vending machine, the state pattern could be used to manage the state of the vending machine, which could be either empty, in operation, or out of order.

Design Principles

"Head First Design Patterns" highlights several important design principles throughout the book. These principles serve as a foundation for the design patterns and their use in effective software development.

  1. Encapsulate What Varies: Identify the parts of your application that may change and separate them from what stays the same.
  2. Favor Composition Over Inheritance: Using object composition and delegation can often lead to more flexible systems than using class inheritance.
  3. Program to Interfaces(Supertypes), Not Implementations: Depend on abstractions, not on concrete classes. This allows changes to the underlying implementations without affecting the interfaces.
  4. Strive for Loosely Coupled Designs Between Objects That Interact: Loosely coupled designs allow changes in one part of the system without dramatically affecting others.
  5. Classes Should Be Open for Extension, but Closed for Modification (Open-Closed Principle): The idea is to allow classes to be easily extended to incorporate new behavior without modifying the existing code.
  6. Depend on Abstractions. Do Not Depend on Concrete Classes (Dependency Inversion Principle): High-level modules should not depend on low-level modules. Both should depend on abstractions.
  7. Only Talk to Your Friends (Principle of Least Knowledge - Law of Demeter): An object should not expose its internal structure through its methods and should only talk to its immediate friends.
  8. Don’t Call Us, We’ll Call You (Hollywood Principle): Lower-level components that perform detail work can be hooked into a system, but high-level components determine when they are needed, and how.
  9. A Class Should Have Only One Reason to Change (Single Responsibility Principle): Every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class.
  10. Strive for Reusable Designs: One of the main benefits of design patterns is that they provide a way to solve issues related to software development using a proven solution. The solution facilitates the development of highly cohesive modules with minimal coupling.


Examples: Strategy Design Pattern

The Strategy pattern is a behavioural design pattern that enables an algorithm's behavior to be selected at runtime. In Java, the Strategy pattern can be implemented using dependency injection, which allows you to inject different strategy classes to change the behavior of the application. Here is a simple example:

Let's assume we're developing an application that can process different types of files (XML, JSON, CSV). We can create a strategy for each file type:


  1. IFileStrategy

Define the IFileStrategy interface. We added "I" prefix to specify that's an interface

package com.kubrik.designPatterns.strategy

public interface IFileStrategy {
    void processFile(String fileName);
};        

2. Json File Strategy

package com.kubrik.designPatterns.strategy


public class JsonFileStrategy implements IFileStrategy {

    @Override
    public void processFile(String fileName) {
        // JSON processing
        System.out.println("Processing JSON file " + fileName);
    }
};        

3. Csv File Strategy

package com.kubrik.designPatterns.strategy

public class CsvFileStrategy implements IFileStrategy {

    @Override
    public void processFile(String fileName) {
        // CSV processing
        System.out.println("Processing CSV file " + fileName);
    }
};        

4. XML File Strategy

package com.kubrik.designPatterns.strategy

public class XMLFileStrategy implements IFileStrategy {
    @Override
    public void processFile(String fileName) {
        // XML processing
        System.out.println("Processing XML file " + fileName);
    }
}
;        

5. File Processor

At this point, we need someone who would be interacting with the files and picking up at run time the appropriate strategy. Let's create our File Processor service.

package com.kubrik.designPatterns.strategy

import java.util.Map;
import java.util.Objects;

public class FileProcessor {

    private final Map<String, IFileStrategy> fileStrategyMap;

    public FileProcessor(Map<String, IFileStrategy> fileStrategyMap) {
        this.fileStrategyMap = fileStrategyMap;
    }

    public void processFile(String fileType, String fileName) {
        IFileStrategy fileStrategy = fileStrategyMap.get(fileType);
        if (Objects.nonNull(fileStrategy)) {
            fileStrategy.processFile(fileName);
        } else {
            throw new IllegalArgumentException("Invalid file type: " + fileType);
        }
    }
}        

In all scenarios, when working within a Spring Boot application, you can utilize the @Service and @Autowired annotations to streamline the creation of instances. These annotations enable Spring's powerful Dependency Injection mechanism, thus saving you from manually orchestrating object creation and association.

6. Testing The Application

Finally, let's test our application.

package com.kubrik.designPatterns.strategy

import java.util.Map;

public class Main {

    public static void main(String[] args) {
   // write your code here

        JsonFileStrategy jsonFileStrategy = new JsonFileStrategy();
        XMLFileStrategy xmlFileStrategy = new XMLFileStrategy();
        CsvFileStrategy csvFileStrategy = new CsvFileStrategy();


        FileProcessor fileProcessor = new FileProcessor(Map.of("json", jsonFileStrategy,"xml",xmlFileStrategy,
                "csv",csvFileStrategy));

        fileProcessor.processFile("json", "MyJsonFile.json");
        fileProcessor.processFile("csv", "MyCSVFile.csv");
        fileProcessor.processFile("xml", "MyXMLFile.xml");

    }
};        

The output:

Processing JSON file MyJsonFile.json
Processing CSV file MyCSVFile.csv
Processing XML file MyXMLFile.xml

Process finished with exit code 0n        

Explanation:

In our example, we've architected an IFileStrategy interface and have subsequently brought to life three distinct strategies for the processing of JSON, XML, and CSV files. Leveraging Spring's dependency injection, we can then weave these strategies into the very fabric of a FileProcessor service.?This is where the true potency of the Strategy pattern is revealed. The FileProcessor service, acting as a conductor, uses the appropriate strategy at runtime to process different types of files. Yet, it remains blissfully unaware of the internal workings of these file strategies, unburdened by the specifics of how they fulfil their tasks.?Its focus is solely on their common interface, processFile, which guarantees a standard method for file processing regardless of the file type. By focusing on this interface, the FileProcessor service can fluidly coordinate the processing of various file types without getting entangled in the intricate details of each specific processing strategy. This epitomizes the key principle of the Strategy pattern – encapsulating the what (the task) from the how (the implementation).

Code repository

Conclusion

Design Patterns, as we've seen, are powerful tools that software developers can use to solve common problems. Understanding these patterns can make one's code more efficient, readable, and maintainable. However, it's essential to remember that while they're beneficial, they are not a cure-all solution and should not be used indiscriminately. They should be viewed as guiding principles, not rigid rules, and used when and where appropriate.

Throughout this introduction, we've referenced the excellent "Head First Design Patterns". The book provides an engaging, interactive, and visually enriched approach to learning about Design Patterns. Whether you're a junior developer or an experienced engineer, this resource offers a wealth of knowledge that can bolster your software development expertise.

We've covered a broad overview of design patterns in this guide. But remember, the journey doesn't end here. In the next edition of #InsideEngineering, we'll dive deeper into each of these patterns, illuminating their intricacies, providing more detailed examples, and discussing the appropriate scenarios for their application. We believe that understanding these patterns in detail can be a game-changer for your coding skills.?

So stay tuned, keep exploring, and continue your journey into the fascinating world of Design Patterns with us!


#Java?#Architecture?#Cloud?#Kubernetes?#Security?#Flexibility?#Scalability?#Resilience?#KubrikDigital?#Kubrik?#Observer #Strategy #Decorator #FactoryMethod #Singleton #Adapter #Facade #TemplateMethod #State #Iterator #Composite #Command #DesignPatterns?#Development #InsideEngineering

AbuSiddik S.

????Certified Scrum Master? | Top Agile Methodologies Voice | AI Content Creator | SAP Hybris Commerce Cloud | GenAI Enthusiast #ProjectManament #RealTimePayment #DirecetDebit #Scrum #Agile #PromptEngineering

1 年

valuable and insightful

回复
Viktor Karpyuk

Founder of Kubrik Digital - kubrikdigital.com & timelogbook.com. Investor and Innovation Leader. Passionate about affordable technology, SaaS and AI.

1 年

Very insightful!

要查看或添加评论,请登录

Kubrik Digital的更多文章

社区洞察

其他会员也浏览了