Object Orientation with Design Patterns

From Sinfronteras
Revision as of 01:22, 10 March 2019 by Adelo Vieira (talk | contribs) (Example)
Jump to: navigation, search

Amilcar Aponte amilcar@cct.ie

Literature


What are design patterns

  • They provide solutions to common software design problems.
  • Generally aimed at solving the problems of object generation and interaction, rather than the larger scale problems of overall software architecture
  • They give generalised solutions in the form of templates that may be applied to real-world problems.


Why use design patters

  • Flexibility: Using design patterns your code becomes more flexible. It helps to provide the correct level of abstraction due to which objects become loosely coupled to each other which makes your code easy to maintain.
  • Reusability: Loosely coupled and cohesive objects and classes can make your code more reusable.
  • Shared Vocabulary:
    • Shared vocabulary makes it easy to share your code and thought with other team members.
    • It creates more understanding between the team members related to the code.
  • Capture best practices (In Amilcar's my opinion, the most important):
    • Design patterns capture solutions that have been successfully applied to problems.
    • By learning these patterns and the related problem, an inexperienced developer learns a lot about software design.
    • These solutions have been tested, used, reused and used again – And they work!


Essential elements

Design patterns essential elements
  • Name:
    • Provides a single and meaningful name to the pattern, and defines a problem and a solution for it.
    • Naming a design pattern helps itself to be referred to others easily. It also becomes easy to provide documentation for and the right vocabulary word makes it easier to think about the design.
  • The Problem:
    • It explains the problem and its context.
    • It might describe specific design problems.
    • Sometimes the problem will include a list of conditions that must be met before it makes sense to apply the pattern.
  • The solution:
    • It describes the elements that make up the design, their relationships, responsibilities, and collaborations.
    • The solution is not the complete code, but it works as a template which can be fulfilled with code.
    • The pattern provides an abstract description of a design problem and how a general arrangement of elements (classes and objects in our case) solves it.
  • Implications:
    • The consequences of a pattern include its impact on a system's flexibility, extensibility, portability or possible trade-offs.
    • Listing these consequences explicitly helps you understand and evaluate them.


Categories of patterns

  • Creational:
    • Creational design patterns are used to design the instantiation process of objects.
    • The creational pattern uses inheritance to vary the object creation.
  • Structural:
    • Structural patterns are concerned with how classes and objects are composed to form larger structures.
    • These patterns are particularly useful for making independently developed class libraries work together.
    • Rather than composing interfaces or implementations, structural object patterns describe ways to compose objects to realize new functionality.
  • Behavioural:
    • Behavioural patterns are concerned with algorithms and the assignment of responsibilities between objects.
    • Behavioural patterns describe not just patterns of objects or classes but also the patterns of communication between them.
    • Some describe how a group of peer objects cooperate to perform a task that no single object can carry out by itself.


Singleton Pattern

https://refactoring.guru/design-patterns/singleton

Singleton is a creational design pattern that lets you ensure that a class has only one instance, while providing a global access point to this instance.


Problem:

The Singleton pattern solves two problems at the same time, violating the Single Responsibility Principle:

  • Problem 1: Ensure that a class has just a single instance.
Why would anyone want to control how many instances a class has? The most common reason for this is to control access to some shared resource, for example, a database or a file.
Here's how it works: imagine that you created an object, but after a while decided to create a new one. Instead of receiving a fresh object, you'll get the one you already created.
Note that this behavior is impossible to implement with a regular constructor since a constructor call must always return a new object by design.
  • Problem 2: Provide a global access point to that instance.
Remember those global variables that you (all right, me) used to store some essential objects? While they're very handy, they're also very unsafe since any code can potentially overwrite the contents of those variables and crash the app.
Just like a global variable, the Singleton pattern lets you access some object from anywhere in the program. However, it also protects that instance from being overwritten by other code.
There's another side to this problem: you don't want the code that solves problem 1 to be scattered all over your program. It's much better to have it within one class, especially if the rest of your code already depends on it.


Solution:

All implementations of the Singleton have these two steps in common:

  • Problem 1: Ensure that a class has just a single instance:
  • Make the default constructor private, to prevent other objects from using the new operator with the Singleton.
  • Static instance of the class
  • Problem 2: Provide a global access point to that instance:
  • 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.

If your code has access to the Singleton class, then it's able to call the Singleton's static method. So whenever that method is called, the same object is always returned.


Applicability:

  • Use the Singleton pattern when a class in your program should have just a single instance available to all clients; for example, a single database object shared by different parts of the program.
The Singleton pattern disables all other means of creating objects of a class except for the special creation method. This method either creates a new object or returns an existing one if it has already been created.
  • Use the Singleton pattern when you need stricter control over global variables.
Unlike global variables, the Singleton pattern guarantees that there's just one instance of a class. Nothing, except for the Singleton class itself, can replace the cached instance.

Note that you can always adjust this limitation and allow creating any number of Singleton instances. The only piece of code that needs changing is the body of the getInstance() method.


Implementations

There are different implementations of the Singleton design pattern:

  • Eager implementation
  • Static Block implementation
  • Lazy Initialisation implementation
  • Thread safe implementation
  • Double Check Locking implementation
  • Bill Pugh implementation
  • Enum implementation


Let's explain the different implementations through an example: The CalendarSingleton

A Calendar is a good example for the Singleton implementation. We need to make sure that every user have access to the same instance of the Calendar class.

Singleton design pattern.png


Eager implementation

CalendarSingleton.java
package calendarsingleton;

import java.util.Map;
import java.util.HashMap;
import java.util.Date;
import java.util.Map.Entry;

public class CalendarSingleton {
    
    // HashMap to save one event per date
    private Map <Date, String> calendar;
    

    /* Singleton implementation:
     * Create a static private instance 
     */
    static private CalendarSingleton instance = new CalendarSingleton();

    
    /* Singleton implementation:
     * Private constructor
     * This means that no one can instantiate it but itself
     */
    private CalendarSingleton () {
        calendar = new HashMap<Date, String>();
    }

    
    // Add event on a particular date
    public void addEvent(Date date, String event) {
        calendar.put(date, event);
    }
    
    // Check event on a particular date
    public String getEvent(Date date) {
        return calendar.get(date);
    }
    
    public String getEvents() {
        String events = "";
        
        for (Entry entry : calendar.entrySet()) {
            events += entry.getKey() + " - " + entry.getValue() + "\n";
        }
        
        return events;
    }
    

    /* Singleton implementation:
     * Add a static public getter for the instance
     */
    public static CalendarSingleton getInstance() {
        return instance;
    }
    
}


User.java
package calendarsingleton;

import java.util.Calendar;

public class User {

    private String name;
    private CalendarSingleton calendar;


    public User(String name) {
        this.name = name;
        // Change: getting the ONE instance of the calendar
        // from the unique universal access to it (static method)
        this.calendar = CalendarSingleton.getInstance();
    }


    public String getName() {
        return name;
    }


    public void setName(String name) {
        this.name = name;
    }


    public CalendarSingleton getCalendar() {
        return calendar;
    }


    // We can get rid of the calendar setter, as there is only
    // one calendar in the whole program.

    // public void setCalendar(CalendarSingleton calendar) {
    //     this.calendar = calendar;
    // }
	
}


Main.java  
package calendarsingleton;

import java.util.Date;

public class Main {

    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        // Creating a date object
        Date d1 = new Date(2019,02,04);

        // Creating a calendar object
        // Change: I don't create a new calendar. I access the one
        // that already exists.
        CalendarSingleton calendar = CalendarSingleton.getInstance();

        // Adding an event to the calendar
        calendar.addEvent(d1, "Amilcar's Class");

        // Retrieving the event on the date
        //System.out.println(calendar.getEvent(d1));

        // Creating a date object
        Date d2 = new Date(2019,02,05);

        // Adding another event to the calendar
        calendar.addEvent(d2, "Greg's Class");

        // Retrieving all events
        //System.out.println(calendar.getEvents());

        // Creating multiple users.
        User u1 = new User("Amilcar");
        User u2 = new User("Greg");
        User u3 = new User("Graham");
        User u4 = new User("Neil");

        // Testing that the calendar belongs to user one
        // actually has the changes
        CalendarSingleton u1Cal = u1.getCalendar();
        System.out.println(u1Cal.getEvents());

    }

}


Se debe notar que en la Eager Implementation the instance is created at compiling time:

public class CalendarSingleton {
    
    // HashMap to save one event per date
    private Map <Date, String> calendar;
    

    /* Singleton implementation:
     * Create a static private instance 
     */
    static private CalendarSingleton instance = new CalendarSingleton();
    .
    .
    .


Static Block implementation

  • Very similar to Eager initialisation, but the instance is inside an static block.
  • This allows the use of a try-catch to handle exceptions in case something goes wrong
        .
        .
        .
        // Adding an static block allows us to 
        // handle exceptions in case it is needed.
        static {
                try {
                        instance = new CalendarStaticBlock();
                } catch(Exception e) {
                        System.out.println(e);
                }
        }
        .
        .
        .

In both cases (Eager and Static block implementations) the instance is created before it is actually used or needed:

  • More memory consumption
  • Not good practice


Lazy Initialisation implementation

  • Verifies first if the instance already exists:
    • If it doesn't, it will create it and return it.
    • If it does, it will return it.
        .
        .
        .	
        // Don't instantiate eagerly at compiling time
        private static CalendarLazy instance = null;
        .
        .
        .
        public static CalendarLazy getInstance() {	
                if (instance == null) {
                        instance = new CalendarLazy();
                }
                return instance;
        }
        .
        .
        .
  • Works well for a single thread environment
  • When it comes to multi-threaded environments, this could crash.
  • Imagine two threads trying to get the same instance simultaneously before it has been created. It could lead to a double instance of the class.


Thread Safe implementation

  • Similar to Lazy Initialisation
  • But this time, we'll add another modifier to the getInstance static method: synchronized
        .
        .
        .
        // The synchronised modifier, will process one request at the time
        // so we can guarantee that only one instance is created
        public static synchronized CalendarThreadSafe getInstance() {
                if (instance == null) {
                        instance = new CalendarThreadSafe();
                }	
                return instance;
        }
        .
        .
        .
  • The Thread Safe implementation works fine but it reduces the performance because of the cost associated with the synchronized method:
    • Imagine to freeze the whole program any time an object tries to access the instance.


Double Check Locking implementation

  • The synchronized block is used inside the if statement with an additional check to ensure that only one instance of the singleton class is created.
	.
	.
	.
	// Instead of freezing the whole program every time a class
	// request the instance calendar, only synchronise when the
	// instance has to be created.
	public static CalendarDoubleCheckLocker getInstance() {
		if(instance == null) {
			// Double check, in case the are multiple thread
			synchronized (CalendarDoubleCheckLocker.class){
				// Accessing the same instance
				if(instance == null) {
					instance = new CalendarDoubleCheckLocker();
				}
			}
		}
		return instance;
	}
	.
	.
	.
  • This approach is very good for multithreaded environments.


Bill Pugh implementation

  • This is a completely different approach from what we've seen so far
  • Instead of verifying if the instance exists, this time we will use an inner static class
  • This inner class will contain and instantiate the outer class
        .
        .
        .
        // Inner class to hold an instance of the outer class
        private static class CalendarHelper{		
                private static CalendarBillPugh instance = new CalendarBillPugh();
        }
        .
        .
        .
        public static CalendarBillPugh getInstance() {
                return CalendarHelper.instance;
        }
        .
        .
        .
  • This approach is very good as it doesn't require synchronisation.


Enum implementation

Introspection and Reflexion

Introspection and reflexion allow us to get access to a class from an object of that type or the class itself. This way, it is perfectly possible to break the above implementation of the singleton pattern accessing the constructor and making it public.

Introspection
try {     
     Class calendarClass = instanceOne.getClass();
     Constructor[] constructors = calendarClass.getDeclaredConstructors();

     for (Constructor constructor : constructors){
          constructor.setAccessible(true);
          instanceTwo = (CalendarSingleton) constructor.newInstance();
          break;
     }
} catch(Exception e){
     e.printStackTrace();
}
Reflexion
try {
     Constructor[] constructors = CalendarSingleton.class.getDeclaredConstructors();

     for (Constructor constructor : constructors) {
          // Below code will destroy the singleton pattern
          constructor.setAccessible(true);
          instanceTwo = (CalendarSingleton) constructor.newInstance();
     }
} catch (Exception e) {
     e.printStackTrace();
}
Enum implementation

Joshua Bloch suggests the use of Enum to implement the Singleton design pattern as Java ensures that any enum value is instantiated only once in a Java program. Since Java Enum values are globally accessible, so is the singleton. The use of Enum avoid the possibility of to breaking singleton pattern using Introspection or Reflexion.

The drawback is that the enum type is somewhat inflexible; for example, it does not allow lazy initialization.

// Completely different approach. Let's use an enum instead
public enum CalendarEnum {
        // HashMap to save one event per date
        private Map <Date, String> calendar;

        // We don't need the instance anymore, just an unique value for the enum
        instance;
        .
        .
        .
        public static CalendarEnum getInstance() {
                return instance;
        }
}


Factory Pattern

https://refactoring.guru/design-patterns/factory-method

Factory Method 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.


Problem

Imagine that you're creating a logistics management application. The first version of your app can only handle transportation by trucks, so the bulk of your code lives inside the Truck class.

After a while, your app becomes pretty popular. Each day you receive dozens of requests from sea transportation companies to incorporate sea logistics into the app.

Great news, right? But how about the code? At present, most of your code is coupled to the Truck class. Adding Ships into the app would require making changes to the entire codebase. Moreover, if later you decide to add another type of transportation to the app, you will probably need to make all of these changes again.

Adding a new class to the program isn’t that simple if the rest of the code is already coupled to existing classes.


Solution

The Factory Method pattern suggests that you replace direct object construction calls (using the new operator) with calls to a special factory method. Don't worry: the objects are still created via the new operator, but it’s being called from within the factory method. Objects returned by a factory method are often referred to as products.

At first glance, this change may look pointless: we just moved the constructor call from one part of the program to another. However, consider this: now you can override the factory method in a subclass and change the class of products being created by the method.

There's a slight limitation though: subclasses may return different types of products only if these products have a common base class or interface. Also, the factory method in the base class should have its return type declared as this interface.

For example, both Truck and Ship classes should implement the Transport interface, which declares a method called deliver. Each class implements this method differently: trucks deliver cargo by land, ships deliver cargo by sea. The factory method in the RoadLogistics class returns truck objects, whereas the factory method in the SeaLogistics class returns ships.

Subclasses can alter the class of objects being returned by the factory method.
All products must follow the same interface.
As long as all product classes implement a common interface, you can pass their objects to the client code without breaking it.


Structure

Factory pattern-Structure.png
  1. The Product declares the interface, which is common to all objects that can be produced by the creator and its subclasses.
  2. Concrete Products are different implementations of the product interface.
  3. The Creator class declares the factory method that returns new product objects. It’s important that the return type of this method matches the product interface.
You can declare the factory method as abstract to force all subclasses to implement their own versions of the method. As an alternative, the base factory method can return some default product type.
Note, despite its name, product creation is not the primary responsibility of the creator. Usually, the creator class already has some core business logic related to products. The factory method helps to decouple this logic from the concrete product classes. Here is an analogy: a large software development company can have a training department for programmers. However, the primary function of the company as a whole is still writing code, not producing programmers.
  1. Concrete Creators override the base factory method so it returns a different type of product.
Note that the factory method doesn’t have to create new instances all the time. It can also return existing objects from a cache, an object pool, or another source.


Example

FactoryPattern.svg

Builder Pattern

https://refactoring.guru/design-patterns/builder

Builder 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.


Problem:

Imagine a complex object that requires laborious, step-by-step initialization of many fields and nested objects. Such initialization code is usually buried inside a monstrous constructor with lots of parameters. Or even worse: scattered all over the client code.

For example, let's think about how to create a House object. To build a simple house, you need to construct four walls and a floor, install a door, fit a pair of windows, and build a roof. But what if you want a bigger, brighter house, with a backyard and other goodies (like a heating system, plumbing, and electrical wiring)?


The simplest solution is to extend the base House class and create a set of subclasses to cover all combinations of the parameters. But eventually you'll end up with a considerable number of subclasses. Any new parameter, such as the porch style, will require growing this hierarchy even more.

You might make the program too complex by creating a subclass for every possible configuration of an object.


There's another approach that doesn't involve breeding subclasses. You can create a giant constructor right in the base House class with all possible parameters that control the house object. While this approach indeed eliminates the need for subclasses, it creates another problem: In most cases most of the parameters will be unused, making the constructor calls pretty ugly. For instance, only a fraction of houses have swimming pools, so the parameters related to swimming pools will be useless nine times out of ten.

The constructor with lots of parameters has its downside: not all the parameters are needed at all times.


Solution:

The Builder pattern suggests that you extract the object construction code out of its own class and move it to separate objects called builders. The pattern organizes object construction into a set of steps (buildWalls, buildDoor, etc.). To create an object, you execute a series of these steps on a builder object. The important part is that you don’t need to call all of the steps. You can call only those steps that are necessary for producing a particular configuration of an object.

The Builder pattern lets you construct complex objects step by step. The Builder doesn’t allow other objects to access the product while it’s being built.


Some of the construction steps might require different implementation when you need to build various representations of the product. For example, walls of a cabin may be built of wood, but the castle walls must be built with stone. In this case, you can create several different builder classes that implement the same set of building steps, but in a different manner. Then you can use these builders in the construction process (i.e., an ordered set of calls to the building steps) to produce different kinds of objects.

Different builders execute the same task in various ways.


Director: You can go further and extract a series of calls to the builder steps you use to construct a product into a separate class called director. The director class defines the order in which to execute the building steps, while the builder provides the implementation for those steps.

Having a director class in your program isn’t strictly necessary. You can always call the building steps in a specific order directly from the client code. However, the director class might be a good place to put various construction routines so you can reuse them across your program.

In addition, the director class completely hides the details of product construction from the client code. The client only needs to associate a builder with a director, launch the construction with the director, and get the result from the builder.


Structure

Builder pattern-Structure.png
  1. The Builder interface declares product construction steps that are common to all types of builders.
  2. Concrete Builders provide different implementations of the construction steps. Concrete builders may produce products that don’t follow the common interface.
  3. Products are resulting objects. Products constructed by different builders don’t have to belong to the same class hierarchy or interface.
  4. The Director class defines the order in which to call construction steps, so you can create and reuse specific configurations of products.
  5. The Client must associate one of the builder objects with the director. Usually, it’s done just once, via parameters of the director’s constructor. Then the director uses that builder object for all further construction. However, there’s an alternative approach for when the client passes the builder object to the production method of the director. In this case, you can use a different builder each time you produce something with the director.

How to Implement

  1. Make sure that you can clearly define the common construction steps for building all available product representations. Otherwise, you won’t be able to proceed with implementing the pattern.
  2. Declare these steps in the base builder interface.
  3. Create a concrete builder class for each of the product representations and implement their construction steps.
  4. Don’t forget about implementing a method for fetching the result of the construction. The reason why this method can’t be declared inside the builder interface is that various builders may construct products that don’t have a common interface. Therefore, you don’t know what would be the return type for such a method. However, if you’re dealing with products from a single hierarchy, the fetching method can be safely added to the base interface.
  5. Think about creating a director class. It may encapsulate various ways to construct a product using the same builder object.
  6. The client code creates both the builder and the director objects. Before construction starts, the client must pass a builder object to the director. Usually, the client does this only once, via parameters of the director’s constructor. The director uses the builder object in all further construction. There’s an alternative approach, where the builder is passed directly to the construction method of the director.
  7. The construction result can be obtained directly from the director only if all products follow the same interface. Otherwise, the client should fetch the result from the builder.