Object Orientation with Design Patterns

From Sinfronteras
Revision as of 18:08, 21 June 2020 by Adelo Vieira (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Contents


https://medium.com/@smagid_allThings/uml-class-diagrams-tutorial-step-by-step-520fd83b300b



Literature

  • El Prof. se refirió a este libro como un clásico, uno de los mejores en Design patterns:
Gamma, E., Helm, R., Johnson, R. & Vissides, J. (1994) Design Patterns: Elements of Reusable Object-Oriented Software. United States: Addison-Wesley
https://www.amazon.co.uk/Design-patterns-elements-reusable-object-oriented/dp/0201633612/ref=sr_1_1?ie=UTF8&qid=1509960461&sr=8-1&keywords=design+patterns




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.


  • There are only a few different design patterns that are ever really followed when developing code. Most of the times when you see code, you will realise that it is just a modification of one of the base design patterns that have been identified throughout the years.



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.



Creational Patterns


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


// The Factory class will be our helper to instantiate the sub classes of card
public class CardCreator {
    // This method is static so all clients can request an instance of the class
    public static Card createCard(String type, int number, String name, int cvv) {

        // If statement to define the adequate type of card
        if (type.equals("Maestro")) {
            return new Maestro(number, name, cvv);
        }
        else if (type.equals("Mastercard")) {
            return new Mastercard(number, name, cvv);
        }
        else if(type.equals("Visa")) {
            return new Visa(number, name, cvv);
        }
        else {
            return null;
        }
        
    }

}




Abstract Factory Pattern



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.



Example



Prototype Pattern


Structural Patterns


Adapter Pattern

  • It is used so that two unrelated interfaces can work together
  • The object that joins this unrelated interface is called an Adapter


  • This approach is widely used when it comes to legacy code, or incompatible interfaces.



Example

The problem:

  • Two companies are merging together, and each one of them has it own set of legacy code that it is not quite compatible.


The solution:

  • The solutionThere are two ways to implement this:
    • Using classes
    • Using objects



Class Adapter
  • Create a new class that extends the class that you want to adapt and implements the interface that you need.
Adapter pattern1.png



Object Adapter
  • Create a new class that implements the interface that you need and has an instance of the class you want to adapt.
Adapter pattern2.png



Facade Pattern

Facade is a structural design pattern that provides a simplified interface to a library, a framework, or any other complex set of classes.



The problem

Imagine that you must make your code work with a broad set of objects that belong to a sophisticated library or framework. Ordinarily, you’d need to initialize all of those objects, keep track of dependencies, execute methods in the correct order, and so on.

As a result, the business logic of your classes would become tightly coupled to the implementation details of 3rd-party classes, making it hard to comprehend and maintain.



Solution

A facade is a class that provides a simple interface to a complex subsystem which contains lots of moving parts. A facade might provide limited functionality in comparison to working with the subsystem directly. However, it includes only those features that clients really care about.

Having a facade is handy when you need to integrate your app with a sophisticated library that has dozens of features, but you just need a tiny bit of its functionality.



Real-World Analogy

When you call a shop to place a phone order, an operator is your facade to all services and departments of the shop. The operator provides you with a simple voice interface to the ordering system, payment gateways, and various delivery services.

Facade pattern1.png



In short

  • The façade is just a front facing interface that helps hide complexity of systems.
  • Whether to use Façade or not is completely dependent on client code.
  • Façade design pattern can be applied at any point of development, usually when the number of interfaces grows and system gets complex.
  • Subsystem interfaces are not aware of Façade and they shouldn't have any reference to the Façade interface.



Example

Suppose we have an application with a set of interfaces to use MySql/Oracle database and to generate different types of reports, such as HTML report, PDF report etc. So we will have different set of interfaces to work with different types of database and report



The problem
  • A client application can use these interfaces to get the required database connection and generate reports.
  • But when the complexity increases or the interface behavior names are confusing, the client application will find it difficult to manage it.
  • So we can apply Facade pattern here and provide a wrapper interface on top of the existing interface to help the client application.



Proxy pattern

Proxy is a structural design pattern that lets you provide a substitute or placeholder for another object. A proxy controls access to the original object, allowing you to perform something either before or after the request gets through to the original object.



Problem

Why would you want to control access to an object? Here is an example: you have a massive object that consumes a vast amount of system resources. You need it from time to time, but not always.

You could implement lazy initialization: create this object only when it's actually needed; but in order to do so, all of the object's clients would need to execute some deferred initialization code (Unfortunately, this would probably cause a lot of code duplication). In an ideal world, we'd want to put this code directly into our object's class, but that isn't always possible. For instance, the class may be part of a closed 3rd-party library.


Proxi pattern1.png



Solution

The Proxy pattern suggests that you create a new proxy class with the same interface as an original service object. Then you update your app so that it passes the proxy object to all of the original object’s clients. Upon receiving a request from a client, the proxy creates a real service object and delegates all the work to it.

But what’s the benefit? If you need to execute something either before or after the primary logic of the class, the proxy lets you do this without changing that class. Since the proxy implements the same interface as the original class, it can be passed to any client that expects a real service object.


Proxi pattern2.png



Structure

  1. The Service Interface declares the interface of the Service. The proxy must follow this interface to be able to disguise itself as a service object.
  2. The Service is a class that provides some useful business logic.
  3. The Proxy class has a reference field that points to a service object. After the proxy finishes its processing (e.g., lazy initialization, logging, access control, caching, etc.), it passes the request to the service object. Usually, proxies manage the full lifecycle of their service objects.
  4. The Client should work with both services and proxies via the same interface. This way you can pass a proxy into any code that expects a service object.
Proxi pattern3.png



Decorator Pattern

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

  • Decorator design pattern is used to modify the functionality of an object at runtime.
  • At the same time, other instances of the same class will not be affected by this, so individual objects get the modified behavior.
  • Decorator design pattern is one of the structural design patterns and uses abstract classes or interface with composition to implement.



The Problem

  • We use inheritance to extend the behavior of an object, but this is done at compile time and its applicable to all the instances of the class.
  • We can't add any new functionality of remove any existing behavior at runtime... this is when the Decorator pattern comes into picture.



Example

The problem:

  • Suppose we need a car that has features of both Sport and Luxury cars
  • We would need to create a new class to extend the basic car
Decorator pattern1.png


The solution:

  • We will use both inheritance and composition to solve the problem
  • Instead of creating a sub class for each particular sub type, we'll create a BasicCar, and a CarDecorator that will both extend the Car interface
  • Then, the different types of cars will be subtypes of the CarDecorator class
  • A key part here is that the decorator will have an instance of another concretion of the target
  • This will allow the decorator to be used consecutive times
Decorator pattern3.png
Decorator pattern2.png


Extending a class is the first thing that comes to mind when you need to alter an object’s behavior. However, inheritance has several serious caveats that you need to be aware of.

  • Inheritance is static. You can’t alter the behavior of an existing object at runtime. You can only replace the whole object with another one that’s created from a different subclass.
  • Subclasses can have just one parent class. In most languages, inheritance doesn’t let a class inherit behaviors of multiple classes at the same time.

One of the ways to overcome these caveats is by using Composition instead of Inheritance. With composition one object has a reference to another and delegates it some work, whereas with inheritance, the object itself is able to do that work, inheriting the behavior from its superclass.

With composition, you can easily substitute the linked “helper” object with another, changing the behavior of the container at runtime. An object can use the behavior of various classes, having references to multiple objects and delegating them all kinds of work.

Composition is the key principle behind many design patterns, including the Decorator. On that note, let’s return to the pattern discussion.



Implications

  • Decorator design pattern is helpful in providing runtime modification abilities and therefore more flexible. It is easy to maintain and extend when there are a greater number of choices.
  • The disadvantage of the decorator pattern is that it uses a lot of similar objects (decorators) this can become confusing.
  • Decorator pattern is used a lot inJava IOclasses, such a sFileReader, BufferedReader etc.



Behavioural Patterns


Chain of Responsibility Pattern

Chain of Responsibility is a behavioral design pattern that lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain.


  • Chain of responsibility pattern is used to achieve lose coupling in software design.
  • A request from a client program is passed to a chain of objects to process it.
  • Then the object in the chain will decide who will be processing the request and whether the request is required to be sent to the next object in the chain or not.



Problem

Imagine that you’re working on an online ordering system. You want to restrict access to the system so only authenticated users can create orders. Also, users who have administrative permissions must have full access to all orders.

After a bit of planning, you realized that these checks must be performed sequentially. The application can attempt to authenticate a user to the system whenever it receives a request that contains the user’s credentials. However, if those credentials aren’t correct and authentication fails, there’s no reason to proceed with any other checks.

Design pattern-Chain of responsibility1.png


During the next few months, you implemented several more of those sequential checks.

  • One of your colleagues suggested that it’s unsafe to pass raw data straight to the ordering system. So you added an extra validation step to sanitize the data in a request.
  • Later, somebody noticed that the system is vulnerable to brute force password cracking. To negate this, you promptly added a check that filters repeated failed requests coming from the same IP address.
  • Someone else suggested that you could speed up the system by returning cached results on repeated requests containing the same data. Hence, you added another check which lets the request pass through to the system only if there’s no suitable cached response.


The code of the checks, which had already looked like a mess, became more and more bloated as you added each new feature. Changing one check sometimes affected the others. Worst of all, when you tried to reuse the checks to protect other components of the system, you had to duplicate some of the code since those components required some of the checks, but not all of them.

The system became very hard to comprehend and expensive to maintain. You struggled with the code for a while, until one day you decided to refactor the whole thing.

Design pattern-Chain of responsibility2.png



Solution

Like many other behavioral design patterns, the Chain of Responsibility relies on transforming particular behaviors into stand-alone objects called handlers. In our case, each check should be extracted to its own class with a single method that performs the check. The request, along with its data, is passed to this method as an argument.

The pattern suggests that you link these handlers into a chain. Each linked handler has a field for storing a reference to the next handler in the chain. In addition to processing a request, handlers pass the request further along the chain. The request travels along the chain until all handlers have had a chance to process it.

Design pattern-Chain of responsibility3.png



Structure

Design pattern-Chain of responsibility-Structure.png
  1. The Handler declares the interface, common for all concrete handlers. It usually contains just a single method for handling requests, but sometimes it may also have another method for setting the next handler on the chain.
  2. The Base Handler is an optional class where you can put the boilerplate code that’s common to all handler classes. Usually, this class defines a field for storing a reference to the next handler. The clients can build a chain by passing a handler to the constructor or setter of the previous handler. The class may also implement the default handling behavior: it can pass execution to the next handler after checking for its existence.
  3. Concrete Handlers contain the actual code for processing requests. Upon receiving a request, each handler must decide whether to process it and, additionally, whether to pass it along the chain. Handlers are usually self-contained and immutable, accepting all necessary data just once via the constructor.
  4. The Client may compose chains just once or compose them dynamically, depending on the application’s logic. Note that a request can be sent to any handler in the chain—it doesn’t have to be the first one.



Example

  • A good example Chain of Responsibility pattern is an ATM machine. The user enters the amount to be dispensed and the machine dispense amount in terms of defined currency bills such as €50, €20, €10.
  • If the user enters an amount that is not multiple of 10, it throws an error.
  • If one element in the chain is not able to process it fully, it sends the request to the next processor in chain to process the remaining request.
  • If the processor is not able to process anything, it just forwards the same request to the next chain.



Command Pattern


Iterator Pattern

  • Iterator pattern in one of the behavioral patterns and it is used to provide a standard way to traverse through a group of Objects.
  • Iterator pattern is widely used in the Java Collection Framework, where the Iterator interface provides methods for traversing through a collection.
  • We've used it also when iterating over Database results.


What does Collection mean?

In programming, a collection is a class used to represent a set of similar data type items as a single unit. These unit classes are used for grouping and managing related objects.

A collection has an underlying data structure that is used for efficient data manipulation and storage. Code readability and maintenance improves when collections are used in logical constructs.


  • Iterator in Collections Framework: Let's remember the iterator in the Collection Framework
  • Collection:
  • Lists
  • Sets
  • Deque
  • Maps - Not technically a collection, though!


However, Iterator pattern is not only about traversing through a collection:

  • We can provide different kind of iterators based on our requirements
  • Iterator pattern hides the actual implementation of traversal through the collection and client programs just use iterator methods



Problem

Collections are one of the most used data types in programming. Nonetheless, a collection is just a container for a group of objects.

Most collections store their elements in simple lists. However, some of them are based on stacks, trees, graphs and other complex data structures.

Various types of collections.

But no matter how a collection is structured, it must provide some way of accessing its elements so that other code can use these elements. There should be a way to go through each element of the collection without accessing the same elements over and over.

This may sound like an easy job if you have a collection based on a list. You just loop over all of the elements. But how do you sequentially traverse elements of a complex data structure, such as a tree? For example, one day you might be just fine with depth-first traversal of a tree. Yet the next day you might require breadth-first traversal. And the next week, you might need something else, like random access to the tree elements.

The same collection can be traversed in several different ways.

Adding more and more traversal algorithms to the collection gradually blurs its primary responsibility, which is efficient data storage. Additionally, some algorithms might be tailored for a specific application, so including them into a generic collection class would be weird.

On the other hand, the client code that’s supposed to work with various collections may not even care how they store their elements. However, since collections all provide different ways of accessing their elements, you have no option other than to couple your code to the specific collection classes.



Solution

The main idea of the Iterator pattern is to extract the traversal behavior of a collection into a separate object called an iterator.

In addition to implementing the algorithm itself, an iterator object encapsulates all of the traversal details, such as the current position and how many elements are left till the end. Because of this, several iterators can go through the same collection at the same time, independently of each other.

Usually, iterators provide one primary method for fetching elements of the collection. The client can keep running this method until it doesn’t return anything, which means that the iterator has traversed all of the elements.

All iterators must implement the same interface. This makes the client code compatible with any collection type or any traversal algorithm as long as there’s a proper iterator. If you need a special way to traverse a collection, you just create a new iterator class, without having to change the collection or the client.

Iterators implement various traversal algorithms. Several iterator objects can traverse the same collection at the same time.



Structure

Structure
  1. The Iterator interface declares the operations required for traversing a collection: fetching the next element, retrieving the current position, restarting iteration, etc.
  2. Concrete Iterators implement specific algorithms for traversing a collection. The iterator object should track the traversal progress on its own. This allows several iterators to traverse the same collection independently of each other.
  3. The Collection interface declares one or multiple methods for getting iterators compatible with the collection. Note that the return type of the methods must be declared as the iterator interface so that the concrete collections can return various kinds of iterators.
  4. Concrete Collections return new instances of a particular concrete iterator class each time the client requests one. You might be wondering, where’s the rest of the collection’s code? Don’t worry, it should be in the same class. It’s just that these details aren’t crucial to the actual pattern, so we’re omitting them.
  5. The Client works with both collections and iterators via their interfaces. This way the client isn’t coupled to concrete classes, allowing you to use various collections and iterators with the same client code. Typically, clients don’t create iterators on their own, but instead get them from collections. Yet, in certain cases, the client can create one directly; for example, when the client defines its own special iterator.



Observer Pattern

Observer is useful when you are interested in the state of an object and want to be notified whenever there is any change. The object that watches the state of another object is called the Observer and the object that is being watched is called the Subject.

  • Subject contains a list of observers to notify of any change in its state, so it should provide methods so observers can register and unregister themselves
  • Subject also contains a method to notify all the observers of any change, either it can send the update while notifying the observer or it can provide another method to get the update.
  • Observer should have a method to set the object to watch and another method that will be used by the Subject to notify them of any updates.
  • A good example of this pattern is the Action Listener in the Java Swing library. The action listener gets notified anytime an event is trigger



Mediator Pattern

https://refactoring.guru/design-patterns/mediator/java/example

Mediator is a behavioral design pattern that reduces coupling between components of a program by making them communicate indirectly, through a special mediator object.



Data Access Patterns


Data Access Object Pattern


Model-View-Controller MVC

  • The MVC is one of the most basic design patterns that you will easily come across during your time developing software.
  • This pattern is only designed to make the maintenance of software easier, it does nothing to actually make the code better or faster.
  • Design Pattern: Model-View-Controller (MVC) Diagram
  • Design Pattern: Model-View-Controller (MVC) Diagram

The three main parts which go into the MVC are the Model, View and the Controller. Each of these plays a distinct role:

  • Model directly manages the data, logic, and rules of the application.
  • View can be any output representation of information, such as a chart or a diagram. Multiple views of the same information are possible, such as a bar chart for management and a tabular view for accountants. (In our case, the JFrame and Components)
  • Controller accepts input and converts it to commands for the model or view.

The main concept behind this approach is that all pieces of code which are for that given element of the MVC should be kept separately. E.g., all elements responsible for generating the user interface should be kept inside of the class file responsible for the View.

When developing the code however, quite often it becomes difficult to create a complete division between these three elements, there will always be some form of an overlap between the class files.

When you are looking at projects that other people have created, there are some tail signs telling that you are looking at a project that uses the MVC. You may see:

  • Files which have the MVC component name added to them, e.g.: MyProgramView.java, MyProgramMode.javal and MyProgramController.java.
  • You may see different folders, one called the Model, View, and one called the Controller.

MVC In Web Applications:

In recent years, frameworks that are available when developing websites often use the MVC structure. This is because the dividing lines between each of the technologies are so clear. The HTML/CSS element can easily be considered the View and the PHP/DB is considered the Model and Controller side.



Software Testing

When developing a piece of software, one of the most essential steps in the process is ensuring that the code that has been developed is thoroughly tested.

It is easily to write some code to do what you want, but it is even more difficult if you need this code to work correctly all the time when used on different systems and with different versions of operating systems.

We will take a look at some of the different types of testing that can be performed on an application that we develop.

Testing is an essential step in the process and after the code has first been developed it must go through a number of different testing phases.

As soon as any changes are introduced into the code, all of the tests must be repeated to ensure that the changes that have been made do not unintentionally hurt other elements of the application that has been developed.

It is very common for one small change to have a big impact on another of different elements in the system.

Although many of the projects which you have been working on up until now have been only you working on the project. It is very easy to see if there is a problem in the code.

If you are working on a larger application with a number of different developers, after all the code has been stuck together by the lead developer in the team, it can be impossible to check if all the bugs in the system have been removed.

This is the job of the tester to ensure that everything is thoroughly tested and no bugs have crept in during the process of merging all of the code together



What can we Test?


Code Level Testing

We can use a number of different tools such as JUnit to ensure that the code we have developed works are expected. Often the code that we develop will not have a GUI attached to it. Sometimes we are just responsible for developing an individual function that is used by another part of the system. In these cases we need to consider what data is passed to the function and what is returned after the function has been called.

We often need to attempt to pass the function that we have developed incorrect data to see how the function will performed if by accident someone sends the wrong data to the function. The code should fail gracefully if the wrong data has been passed to the function. The code should not explode and suddenly crash the application.



User Interface Testing

If you are working on an application that does have a GUI attached to it, we need to test all of the different "what if" scenarios that may happen. Although we may think the code is working correctly if the user clicks a button and a window opens as expected

  • But what happens if the user decides to click the button twice and open the window twice.
  • We need to consider what happens to the data that is in the screen that has just opened.
  • Are we going to corrupt the data because the window has opened twice. Does that system just open up the same window again and bring it to the foreground or does the application crash and throw an error to the user.



Environment Testing

Although we think that all users have the latest version of an operating system and also have all of the correct packages on their system, Often the user will not have all of these installed on their system and they will then attempt to run the software that you have created.

  • We need to consider all of the different operating systems that exist on the market (Even the older ones that you may not think people are using!)
  • What happens if the user does not have the correct software installed before they attempt to install the software you made. Does your system gracefully tell them exactly what they need to download and where they can get it? Or does your system throw out a cryptic error that the user will not be able to understand?



Missing Files

  • Many of the applications that we develop rely on some sort of a file to store user data or to store configuration data.
  • An element of this process that should always be considered is what happens if the files are missing from the system. Does the system automatically regenerate the files allowing the user to continue or does the user need to manually attempt to recreate the configuration files that you have created.
  • The system should be able to handle the issue of files becoming missing in a system. This can occur very easily if the user decides to install the application in a non standard directory.



Handling Errors

  • When developing our code, we often need to think of all the different situations where things can go wrong in our systems.
  • If something does go wrong, we need to nicely inform the user that something has just gone wrong and tell them of the steps that they need to perform before they continue with the software.
  • The user should never be given cryptic errors, often poorly developed software will provide the user will errors messages that do not really explain to the user what has just gone wrong.
  • All errors that your system generates should have some sort of unique ID attached to it. If someone decides to log a bug report for you, at least you will have some insight to roughly where the error has occurred.



Anonymous Usage Statistics

  • In recent years, systems that generate errors and store them locally on the users device provide the user with an option to send usage statistics back to the developers of the software.
  • Everytime something goes wrong in the system, a log file is stored which contains information about the error which has just occurred and additional information about the environment which the software is operating.
  • Once every so often the application will call back to a central server and upload the log files that have been generated by the users. These logs can provide a unique insight into the problems which the users have been experiencing without the need for the user to actually collect and send the data themselves.



Methods of Testing

When testing software we have two different main approaches which we can take, Black Box Testing and White Box Testing.



Black Box Testing

https://en.wikipedia.org/wiki/Black-box_testing

Black-box testing is a method of software testing that examines the functionality of an application without peering into its internal structures or workings. This method of test can be applied to virtually every level of software testing: unit, integration, system and acceptance. It typically comprises most if not all higher level testing, but can also dominate unit testing as well.



White Box Testing

https://en.wikipedia.org/wiki/White_box_(software_engineering)

A white box (or glass box, clear box, or open box) is a subsystem whose internals can be viewed but usually not be altered.

Having access to the subsystem internals in general makes the subsystem easier to understand but also easier to hack; for example, if a programmer can examine source code, weaknesses in an algorithm are much easier to discover. That makes white box testing much more effective than black box testing but considerably more difficult from the sophistication needed on the part of the tester to understand the subsystem.



Runtime Errors

When working with software quite of the software that we make works well but when the software is working we often have other issues which appear.

The compiler does not tell us of these issues, we only see these issues under specific circumstances such as a specific version of the JDK or operating system which we are currently running.



Transient Fault

Transient faults are errors that occur because of some temporary condition such as network connectivity issues or service unavailability. Typically, if you retry the operation that resulted in a transient error a short time later, you find that the error has disappeared.

Many of the errors which users come across have nothing to do with the software that has been developed but are related to the environment they are working in.

If you have an application that requires an internet connection, if the internet connection goes down your application may crash.

There is nothing wrong with the code that has been developed, but you can never be sure what environment the user has. If the internet connection goes away and then suddenly returns, you application should be designed to be able to reattempt the connection process



Logging Errors

When working with errors in our code, typically we just dump out all of the errors to the console and ignore them. If we think about the people who are using the software and the conversations that they could have with technical support, we may need to think a little bit harder of how we treat this information.

When writing Java we often just add a try catch, and print out a simple error to the console to say something went wrong. If a person was calling up a support line, they would need to have an error code which makes sense to the person on the end of the phone.

Good practice would be to give a unique error code inside of every try catch, so when something goes wrong the developer will be able to trace what the error means.

When our application crashes, typically a stack trace is dumped out to the console. Normally we just ignore this but the information it contains is actually very relevant to diagnose the issue that the user is having.

Good software should be able to capture the log file and return it back to the developers so they can further investigate the problem.



Java Logging API

http://docs.oracle.com/javase/7/docs/technotes/guides/logging/

The Java™ Logging APIs, introduced in package java.util.logging, facilitate software servicing and maintenance at customer sites by producing log reports suitable for analysis by end users, system administrators, field service engineers, and software development teams. The Logging APIs capture information such as security failures, configuration errors, performance bottlenecks, and/or bugs in the application or platform.

The core package includes support for delivering plain text or XML-formatted log records to memory, output streams, consoles, files, and sockets. In addition, the logging APIs are capable of interacting with logging services that already exist on the host operating system.



Logger Levels

When working with the logger, we can specify how severe the error that has occurred is to the system.

  • SEVERE (highest level)
  • WARNING
  • INFO
  • CONFIG



Logger Imports
  • import java.io.IOException;
  • import java.util.logging.Level;
  • import java.util.logging.Logger;



Logger Instance

The first thing we need to do is to create an instance of the logger. You will notice to the right hand side the name of the class we are currently testing is passed. In this case it was called "LoggerTest.class"

Logger LOGGER = Logger.getLogger(LoggerTest.class.getName());



Creating info and warning messages

Instead of simply printing to the console, we can use the logger framework to print out info and warning messages:

LOGGER.info("Logger Name: "+LOGGER.getName());
LOGGER.warning("Can cause ArrayIndexOutOfBoundsException");



Catching an Error

If something goes wrong during the process of getting an element from an array, we can surround it all up in a try catch and use the logger to print out a custom error message telling us that the index was out of bounds instead of using the default Java error message.

Index out of bounds custom error:

...
                int []a = {1,2,3};
		int index = 4;

		LOGGER.config("index is set to "+index);
		try{
		     System.out.println(a[index]);
		}catch(ArrayIndexOutOfBoundsException ex){
		     LOGGER.log(Level.SEVERE, "Exception occur", ex);
		}
...



JUnit Testing

When developing software the biggest issue that we always have is centered around testing to ensure that everything is working the way we expected it to.

One method that is typically used is the JUnit framework. This allows us to specify what a specific method in our program should return, and if we get the answer that we are expecting then the test will pass, if we don't then the test will fail.

Eclipse by default comes with the JUnit testing process. Very view changes need to be made to create a test method to assess if your methods work correctly.

Para entender el funcionamiento de JUnit, veamos el siguiente ejemplo:

We will begin the process with a very simple program which has one method called sum which will take two numbers add them together and then return them back.

JUnitExample.java
public class JUnitExample {
	
	public JUnitExample(){
		int answer = sum(1,1);
		System.out.println(answer);
	}
	public int sum(int x, int y){
		return x+y;
	}
	
	public static void main(String[] args) {
		new JUnitExample();
	}
}


Creating the test case: Looking at the code, we know that this method will work, but just to be sure we will make our own custom test case:

  • In Eclipse right click on the file name and then choose New -> JUnit Test Case
  • Another window will then open and you can just click "Finish" to get right to the code.
  • En mi caso (creo que debido a que era el primer Unit Test en el proyecto) se abrió otra ventana: «JUnit 5 is not in the build pathe. Do you want to add it? Al hacer click en OK:
    • Se agregó la Librería JUnit 5 al proyecto,
    • y se un nuevo archivo (una «class») llamado «JUnitExampleTest.java». Inside of this file we will create a new method which will be used as the test method.
JUnit - Creating the test case


Creating the JUnit test method: Inside of «JUnitExampleTest.java», we want to create a method called testSum. Any method which is created that has the word "test" at the start of it is considered to be a JUnit test method:

JUnitExampleTest.java
import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;

class JUnitExampleTest {

	@Test
	void test() {
		JUnitExample s = new JUnitExample();
		int answer = s.sum(1,1);
		int expected = 2;
		assertEquals(expected, answer);
	}

}


Running the Test:

Now that we have set up the test method, we can now run the test class as a test case. This is different to running it as a normal java class. Right click on the test class on the left hand side inside of your package explorer. En el panel que se abre ir a "Run as" and then "JUnit Test". This will then run the program as a test case.


Viewing the results:

After the test has run, we can then look on the left hand side of eclipse (en el mismo espacio en donde se encuentra el Package Explorer(Workspace) pero en la pestaña correspondiente a JUnit) and we will be able to see how the test performed. As both of the expected and the answer variables both equaled 2, the test passed. A green bar indicates that the test went successfully!

JUnit - Viewing the results



Automated Testing with Sikuli

Automated testing solutions such as Sikuli allow you to define a number of steps which you can run through whenever you want to test the application. These steps can be any of the tasks you would normally complete such as opening a web page, clicking a register link and then filling out all of the form fields and pressing the submit button.

Sikuli allows you to automate this process by taking pictures of the different locations you wish to click and then running through all of the different clicks as a script. Sikuli is a visual based tool so anything that you take a picture of must look exactly the same the next time you want to run the script again. An example of where this can cause an issue is an icon on your desktop. If you decide to take a picture of an icon on your desktop and then the next week you change the colour of your desktop background, the script will not run because it will not be able to find the icon that you originally took a picture of.



Downloading and Installing Sikuli


Creating an Automated Test Script


Naming our variables

When designing our software, we always talked about the conventions we can take when we are naming our variables. This all really depends on the company that you are working for and how they like their variables to be named.



Coupled vs Decoupled Solution

We will take a look at two different approaches we can take when creating code.

Coupled Solutions: This would be when two different classes are interacting with each other very closely, their methods are linked together with each other and it would take some time for a developer to find all the different parts where the code connects with each other.

Decoupled Solutions: This is when two different classes are very separate and do not connect much with each other. With decoupled code, it is very easy to find all of the connections between two classes and very easily separate them.

A very good example of this is creating an action listener for a JButton in our program. If you ever search around the web you will find that there is not one single way of creating an action listener, it really depends which approach you would like to take, coupled or decoupled.

Coupled Solutions:

Notice how in the coupled solution, the action listener needs to be implements for the ENTIRE JFrame

public class CoupledSolution extends JFrame implements ActionListener{

This means that our entire JFrame becomes an ActionListener. This is like adding extra abilities onto our JFrame. The downside of this means that our JFrame now needs to have extra code added to it in the form of the actionPerformed method to be able to handle all of the clicks which are made in the GUI.

Notice how in the COUPLED solution, we only have one action performed, but it has to have lots of IF statements to find out exactly which button has just been clicked!

The actionPerformed method needs to handle all of the different clicks made for the entire program. This requires us to have lots of different IF statements inside of the actionPerformed. This makes it very tightly coupled as the one method now needs to do all the work for the entire JFrame.

import javax.swing.JFrame;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;


// Notice how in the coupled solution, the action listener needs
// to be implements for the ENTIRE JFrame
public class ButtonCoupledSolution extends JFrame implements ActionListener{

	public ButtonCoupledSolution(){
		
		setSize(200,200);
		
		// Create the button as usual
		JButton btn2 = new JButton("Click me!!");
		this.add(btn2);
		btn2.setActionCommand("okbutton");
		btn2.addActionListener(this);
		
		
		setVisible(true);
		
	}
	
	public static void main(String[] args) {
		new ButtonCoupledSolution();
	}
	
	// Notice how in the COUPLED solution, we only have one 
	// action performed, but it has to have lots of IF statements to
	// find out exactly which butto has just been clicked!
	@Override
	public void actionPerformed(ActionEvent e) {
		if(e.getActionCommand().equals("okbutton")){
			System.out.println("The button was clicked");
		}
		
		else if (e.getActionCommand().equals("cancel")){
			System.out.println("Cancel button clicked");
		}
	}

}


Decoupled Solution: Notice how in the decoupled solution the JFrame does not need to implement the ActionListener class. All of the code needed to add a listener to the button and also handle the clicks when the actionPerformed method is called are all kept inside of the same block of code.

This makes the code very easy to maintain, because all of the inner workings of the button click are all kept inside of the same block of code, we do not need to dig through the code to find the implements ActionListener part and also the actionPerformed method.

Each button has their own ActionListener and their own actionPerformed method. Each actionPerformed only has one job, to handle the clicks for that one button.

When we are working with the decoupled solution, it is a lot easier to maintain because the developer can easily see everything associated to that one button.

If the developer wants to change what happens when the button is clicked, it is very easy because everything to do with that button is all kept together.

However, one way (Coupled or Decoupled) is not better than the other way, it is personal choice as to which approach you would like to use!

import javax.swing.JFrame;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;


// Notice how this line does NOT implement the action listener
public class ButtonDecoupledSolution extends JFrame {

	
	public ButtonDecoupledSolution(){
		
		setSize(200,200);
		
		// Create the button as usual
		JButton btn2= new JButton("Click me!!");
		this.add(btn2);
			// We are sticking an action listener and a public void actionPerformed
			// all directly onto the button.
			// The action performed only has one job, respond to just this button!
			btn2.addActionListener(new ActionListener(){
					@Override
					public void actionPerformed(ActionEvent arg0) {
						System.out.println("The button has been clicked");
					}
				});

		
		setVisible(true);
		
	}
	
	public static void main(String[] args) {
            new ButtonDecoupledSolution();
	}
}