Difference between revisions of "Object Orientation with Design Patterns"

From Sinfronteras
Jump to: navigation, search
Line 567: Line 567:
 
:: 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.
 
:: 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.
 
:: 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.
{{ordered list|start=4
+
 
| Concrete Creators override the base factory method so it returns a different type of product.
+
# <li value="4">
 +
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.
 
: 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.
}}
+
</li>

Revision as of 20:34, 26 February 2019

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.

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.