Mohamad Abuzaid 1 year ago
mohamad-abuzaid #general

Design Patterns - [1] Creational

Overview on software design patterns and a closer look at the Creational pattern


Design patterns are solutions to common problems that arise in software development. They are reusable and adaptable templates for designing software systems. The goal of design patterns is to improve the overall quality of the code and to make the development process more efficient and easier to maintain.

There are three main types of design patterns:

  1. Creational design patterns: These patterns deal with object creation mechanisms, trying to create objects in a manner suitable to the situation. Examples include the Singleton, Factory Method, and Abstract Factory patterns.
  2. Structural design patterns: These patterns deal with object composition and structure. Examples include the Decorator, Adapter, and Bridge patterns.
  3. Behavioral design patterns: These patterns deal with communication between objects, focusing on the ways objects interact and operate together. Examples include the Observer, Command, and Strategy patterns.
In this article we will focus on Creational Design Patterns and will discuss the other two in future articles.
Samples in this articles are in Kotlin. However, design patterns concepts can be applied to any other programming language or framework.

[1] Creational Design Patterns


Creational design patterns are a category of design patterns that deal with object creation mechanisms, trying to create objects in a manner suitable to the situation. The main aim of these patterns is to provide a flexible way of creating objects while hiding the creation logic from the client code.

Here are a few examples of creational design patterns:

  1. Singleton pattern: This pattern ensures that a class has only one instance, while providing a global point of access to this instance. This pattern is useful in cases where a single instance of a class must coordinate actions across the system.
  2. Factory Method pattern: This pattern provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created. It is a factory of factories.
  3. Abstract Factory pattern: This pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. It provides a way to encapsulate a group of individual factories that have a common theme without specifying their concrete classes.
  4. Builder pattern: This pattern separates the construction of a complex object from its representation, so that the same construction process can create different representations.
  5. Prototype pattern: This pattern involves creating objects by cloning a prototypical instance, rather than calling a constructor. It allows for creating new objects by copying or cloning a previously created object, rather than creating a new one from scratch.

Each of these patterns has its own strengths and weaknesses and should be used depending on the specific requirements of the project. However, by understanding the creational design patterns, developers can choose the most suitable approach for creating objects, resulting in a more flexible and maintainable code.

----------------------------

(A) Singleton Pattern


The Singleton pattern is a creational design pattern that ensures a class has only one instance while providing a global point of access to this instance. This pattern is useful in cases where a single instance of a class must coordinate actions across the system.

The main idea behind the Singleton pattern is to have a private constructor and a static instance variable. The instance variable is initialized when the class is loaded, and a static method is provided to return the instance. This method creates the instance if it has not already been created and returns it, ensuring that there is only one instance of the class.

Here's an example of the Singleton pattern in Kotlin:

class Singleton private constructor() {
    private var data: Int = 0


    companion object {
        private var instance: Singleton? = null
        fun getInstance(): Singleton {
            if (instance == null) {
                instance = Singleton()
            }
            return instance!!
        }
    }
}


In this example, the Singleton class has a private constructor and a companion object. The companion object provides a static method getInstance that creates an instance of the Singleton class if it has not already been created, and returns it. The instance variable instance is declared as nullable and is initialized to null. The !! operator is used to safely access the value of instance, ensuring that it will never be null in the getInstance method.

Note: In Kotlin, you can create a singleton class simply by declaring your class as object.

The Singleton pattern is a simple and effective way to ensure that there is only one instance of a class in the system. However, it's important to be mindful of its limitations and the potential for misuse, as it can lead to tight coupling between components and make testing and maintenance more difficult.


* Disadvantages:

The Singleton pattern has a few disadvantages, which include:

  1. Tight coupling: It creates a tight coupling between the class and its clients. This makes it difficult to change the implementation of the Singleton class, as it may affect other parts of the system that are dependent on it.
  2. Testing difficulties: It can make testing more difficult, as it can be challenging to isolate and test individual components of the system when they are dependent on a single instance of a class.
  3. Global state: It introduces global state into the system, which can make it difficult to understand the interactions between components and the order in which they are executed.
  4. Resource contention: It can cause resource contention and race conditions if multiple threads attempt to access the single instance of the class simultaneously.
  5. Inflexibility: It can be inflexible, as it can be difficult to change the implementation of the Singleton class or to introduce alternative implementations.

In conclusion, the Singleton pattern should be used with caution, as it has a number of disadvantages that can negatively impact the maintainability and scalability of a system. It is important to consider alternative design patterns, such as Dependency Injection or Service Locator, which can provide a more flexible and maintainable solution in certain situations.

------------------------------

(B) Factory Pattern


The Factory Method pattern is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created. The main idea behind this pattern is to delegate the responsibility of object creation to its subclasses.

The Factory Method pattern consists of a factory interface that defines a method for creating objects, and concrete factory classes that implement the factory method and return an instance of the product. The client code uses the factory interface to create objects, without having to know the specific class of the product that will be returned.

Here's an example of the Factory Method pattern in Kotlin:

interface Animal {
    fun speak()
}


class Dog: Animal {
    override fun speak() {
        println("Bark")
    }
}


class Cat: Animal {
    override fun speak() {
        println("Meow")
    }
}


interface AnimalFactory {
    fun createAnimal(): Animal
}


class DogFactory: AnimalFactory {
    override fun createAnimal(): Animal {
        return Dog()
    }
}


class CatFactory: AnimalFactory {
    override fun createAnimal(): Animal {
        return Cat()
    }
}


In this example, the Animal interface defines the methods that all animal objects must implement, and the Dog and Cat classes are concrete implementations of this interface. The AnimalFactory interface defines a method for creating animal objects, and the DogFactory and CatFactory classes are concrete implementations of this interface that return instances of Dog and Cat, respectively. The client code uses the AnimalFactory interface to create animal objects, without having to know the specific class of the animal that will be returned.

The Factory Method pattern provides a flexible way of creating objects, as it allows for the creation of objects to be delegated to its subclasses. This can make the code more maintainable and testable, as changes to the creation logic can be made in the factory classes without affecting the client code.


* Disadvantages:


The Factory Method pattern has a few disadvantages, which include:

  1. Increased complexity: it can introduce additional complexity into the code, as it requires the creation of multiple factory classes, and the client code must be aware of the factory interface. This can make the code harder to understand and maintain.
  2. Tight coupling: It can result in tight coupling between the factory classes and the products they create. This can make it difficult to change the implementation of the products or to add new products, as this may require changes to the factory classes.
  3. Code duplication: It can result in code duplication, as the implementation of the products may be similar, but duplicated in each of the factory classes. This can lead to increased maintenance costs and a higher likelihood of bugs.
  4. Unnecessary abstraction: It can introduce unnecessary abstraction into the code, as the factory interface and classes can be an unnecessary level of abstraction for simple use cases.

-----------------------------

(C) Builder Pattern


The Builder pattern is a creational design pattern that allows for the creation of complex objects in a step-by-step manner, through the use of a builder object. The main idea behind this pattern is to separate the construction of a complex object from its representation, so that the same construction process can create different representations.

The Builder pattern consists of a builder interface that defines the methods for building the object, a concrete builder class that implements the builder interface and creates the object, a director class that oversees the construction process, and a product class that represents the final object.

Here's an example of the Builder pattern in Kotlin:

class Product {
    private val parts = mutableListOf<String>()


    fun add(part: String) {
        parts.add(part)
    }


    override fun toString(): String {
        return parts.joinToString(", ")
    }
}


interface Builder {
    fun buildPartA()
    fun buildPartB()
    fun buildPartC()
    fun getResult(): Product
}


class ConcreteBuilder: Builder {
    private val product = Product()


    override fun buildPartA() {
        product.add("Part A")
    }


    override fun buildPartB() {
        product.add("Part B")
    }


    override fun buildPartC() {
        product.add("Part C")
    }


    override fun getResult(): Product {
        return product
    }
}


class Director {
    private lateinit var builder: Builder


    fun setBuilder(builder: Builder) {
        this.builder = builder
    }


    fun buildMinimalProduct() {
        builder.buildPartA()
    }


    fun buildFullProduct() {
        builder.buildPartA()
        builder.buildPartB()
        builder.buildPartC()
    }
}


In this example, the Product class represents the final object, and it contains a list of parts. The Builder interface defines the methods for building the object, and the ConcreteBuilder class is a concrete implementation of this interface that creates an instance of the Product class and adds parts to it. The Director class oversees the construction process, and it uses the Builder interface to build the object.

The Builder pattern allows for the creation of complex objects in a step-by-step manner, through the use of a builder object. This can make the code more maintainable and readable, as the construction logic is separated from the representation of the object.


* Disadvantages:


The Builder pattern has a few disadvantages:

  1. Increased Complexity: It involves multiple classes and can introduce additional complexity into the code. The client code must be aware of the builder interface and the construction process, which can make the code more difficult to understand and maintain.
  2. Limited Reusability: It is designed to be used for a specific type of object and cannot be easily adapted for use with other types of objects. This limits its reuse potential.
  3. Increased Code Size: It requires the creation of multiple classes, including a builder interface, concrete builder, director, and product, which can lead to an increase in code size and maintainability issues.
  4. Unnecessary Overhead: In cases where the construction process is simple and the final object only requires a few parameters, using the Builder pattern can be seen as an unnecessary overhead.

---------------------------------

(D) Prototype Pattern


The Prototype pattern is a creational design pattern that allows for the creation of new objects by cloning existing objects, instead of creating new objects from scratch. The main idea behind this pattern is to avoid the overhead of creating new objects and to reuse existing objects whenever possible.

The Prototype pattern consists of a prototype interface that defines a cloning method, concrete prototypes that implement the prototype interface, and a client class that uses the prototypes to create new objects.

Here's an example of the Prototype pattern in Kotlin:

interface Prototype {
    fun clone(): Prototype
}


class ConcretePrototypeA(val value: String) : Prototype {
    override fun clone(): Prototype {
        return ConcretePrototypeA(value)
    }
}


class ConcretePrototypeB(val value: Int) : Prototype {
    override fun clone(): Prototype {
        return ConcretePrototypeB(value)
    }
}


class Client {
    private val prototypes = HashMap<String, Prototype>()


    fun addPrototype(name: String, prototype: Prototype) {
        prototypes[name] = prototype
    }


    fun createObject(name: String): Prototype {
        val prototype = prototypes[name]
        return prototype?.clone() ?: throw Exception("Prototype with name $name not found")
    }
}


In this example, the Prototype interface defines a cloning method, and the ConcretePrototypeA and ConcretePrototypeB classes are concrete implementations of this interface. The Client class uses the prototypes to create new objects, and it stores a collection of prototypes in a hash map, where the key is the name of the prototype and the value is the prototype object itself.

The Prototype pattern can be useful for creating new objects from existing objects, as it avoids the overhead of creating new objects from scratch. However, it can also make the code more difficult to maintain, as changes made to the prototypes will affect all instances of the prototypes.


* Disadvantages:

The Prototype pattern has a few disadvantages:

  1. Increased Complexity: It involves multiple classes and can introduce additional complexity into the code. The client code must be aware of the prototype interface and the cloning process, which can make the code more difficult to understand and maintain.
  2. Performance Overhead: It involves cloning objects, which can be computationally expensive and slow down the program. Additionally, deep copying objects can consume a lot of memory, especially if the objects are large.
  3. Limited Reusability: It is designed to be used for a specific type of object and cannot be easily adapted for use with other types of objects. This limits its reuse potential.
  4. Maintenance Issues: It can lead to maintenance issues if the prototypes are not designed carefully, as changes made to the prototypes will affect all instances of the prototypes.
  5. Object Immutability: Cloning objects can result in unexpected behavior if the objects being cloned are mutable. This can lead to inconsistencies and bugs if the client code is not aware of the cloning process.

-------------------------

Takeaway

It's important to note that design patterns are not a one-size-fits-all solution and they should be used judiciously. Each pattern has its own strengths and weaknesses, and it's up to the developer to choose the right pattern for the right situation. The use of design patterns can make the development process faster and easier, but only if they are used correctly.

----------


Next Part ==> Design Patterns - [2] Structural

1
714
Kotlin's Interoperability with Java (2/3)

Kotlin's Interoperability with Java (2/3)

1675112374.jpg
Mohamad Abuzaid
7 months ago
Jetpack Compose Animation

Jetpack Compose Animation

1675112374.jpg
Mohamad Abuzaid
3 months ago
Security in Android App Development (3/3)

Security in Android App Development (3/3)

1675112374.jpg
Mohamad Abuzaid
7 months ago
Effective UI/UX Design in Android Apps (3/3)

Effective UI/UX Design in Android Apps (3/3)

1675112374.jpg
Mohamad Abuzaid
7 months ago
Security in Android App Development (1/3)

Security in Android App Development (1/3)

1675112374.jpg
Mohamad Abuzaid
7 months ago