Design patterns for Automation framework

Creational design patterns

Singleton design Pattern

Use cases

  1. Keep track of same driver instance throughout execution.

  2. DBMS connectivity.

  3. Loading external files like properties, excel etc once rather than loading again and again.

  4. Logger.

Flow

  • Create a class

  • Create a static private variable

  • make default constructor as private

  • create a public static synchronised method to create object

    • add a condition to check whether object is created or not
package com.journaldev.singleton;

public class SingleTonClass {

    private static SingleTonClass instance;

    private SingleTonClass(){}

    public static synchronized SingleTonClass getInstance() {
        if (instance == null) {
            instance = new SingleTonClass();
        }
        return instance;
    }

}
public class DBUtils {

    private static String connection;
    public static String getConnection() {
        return connection;
    }
    public static void  getConnection() {
        if (connection == null){
            //write connection information
            connection = "Connected";
        } else {
            System.out.println("All ready Connected");
        }
    }
}

Factory Design Pattern

The factory design pattern is used when we have a superclass with multiple subclasses and based on input, we need to return one of the subclasses.

  1. Factory design pattern provides approach to code for interface rather than implementation.

  2. Factory pattern removes the instantiation of actual implementation classes from client code. Factory pattern makes our code more robust, less coupled and easy to extend. For example, we can easily change PC class implementation because client program is unaware of this.

  3. Factory pattern provides abstraction between implementation and client classes through inheritance.

Flow

  • Create an abstract class

  • Create sub classes

  • Create a factory class with static method to get target subclass

public abstract class DriverManager {
    protected WebDriver driver;
    public WebDriver getDriver() {
        return driver;
    }    
}

public class ChromeDriverManager extends DriverManager {
    public ChromeDriverManager() {
        WebDriverManager.chromedriver().setup();
        driver = new ChromeDriver();
    }
}

public class DriverManagerFactory {

    public static DriverManager getManager(DriverType type) {
        DriverManager driverManager = null;

        switch (type) {
            case CHROME:
            driverManager = new ChromeDriverManager(); 
            case FIREFOX:
            driverManager = new FirefoxDriverManager(); 
            case EDGE:
            driverManager = new EdgeDriverManager(); 
            default:
            break;
        }
        return driverManager;
    }
}

public static void main(String[] args){

  DriverManager chromeDriver =  DriverManagerFactor.getManager("CHROME");
  DriverManager ch

}

Builder Pattern

  1. Create a class (ex: Computer)

  2. Create a static nested class(ex:Computer builder) and then copy all the arguments from the outer class to the Builder class.

    1. Create setters with return type as BuilderClass

    2. Create build method which return class Object

  3. Create a constructor in main class which takes static nested class as argument and assign all values

public class Computer {

    //required parameters
    private String HDD;
    private String RAM;

    //optional parameters
    private boolean isGraphicsCardEnabled;
    private boolean isBluetoothEnabled;


    public String getHDD() {
        return HDD;
    }

    public String getRAM() {
        return RAM;
    }

    public boolean isGraphicsCardEnabled() {
        return isGraphicsCardEnabled;
    }

    public boolean isBluetoothEnabled() {
        return isBluetoothEnabled;
    }

    private Computer(ComputerBuilder builder) {
        this.HDD=builder.HDD;
        this.RAM=builder.RAM;
        this.isGraphicsCardEnabled=builder.isGraphicsCardEnabled;
        this.isBluetoothEnabled=builder.isBluetoothEnabled;
    }

    //Builder Class
    public static class ComputerBuilder{

        // required parameters
        private String HDD;
        private String RAM;

        // optional parameters
        private boolean isGraphicsCardEnabled;
        private boolean isBluetoothEnabled;

        public ComputerBuilder(String hdd, String ram){
            this.HDD=hdd;
            this.RAM=ram;
        }

        public ComputerBuilder setGraphicsCardEnabled(boolean isGraphicsCardEnabled) {
            this.isGraphicsCardEnabled = isGraphicsCardEnabled;
            return this;
        }

        public ComputerBuilder setBluetoothEnabled(boolean isBluetoothEnabled) {
            this.isBluetoothEnabled = isBluetoothEnabled;
            return this;
        }

        public Computer build(){
            return new Computer(this);
        }

    }

}

public class TestBuilderPattern {

    public static void main(String[] args) {
        //Using builder to get the object in a single line of code and 
                //without any inconsistent state or arguments management issues        
        Computer comp = new Computer.ComputerBuilder(
                "500 GB", "2 GB").setBluetoothEnabled(true)
                .setGraphicsCardEnabled(true).build();
    }

}

Structural Design Pattern

Proxy design pattern

  • Create an interface

  • Create a class and implement it

  • Create a proxy class which implements interface

    • Create object with interface type

    • In the constructor define rules to initiate object

    • Implement method

public interface OrderComponent {
    String placeOrder();
}
public class OrderComponentProxy implements OrderComponent {

    private static final List<String> EXCLUDED = Arrays.asList("PROD", "STAGING");
    private OrderComponent orderComponent;
    public OrderComponentProxy(){
        String currentEnv = System.getProperty("env"); // DEV / QA / PROD / STAGING
        if(!EXCLUDED.contains(currentEnv)){
            this.orderComponent = new OrderComponentReal();
        }
    }
    @Override
    public String placeOrder() {
        if(Objects.nonNull(this.orderComponent)){
            return this.orderComponent.placeOrder();
        }else{
            return "SKIPPED";
        }
    }
}
public class OrderComponentReal implements OrderComponent {
    public OrderComponentReal(){
    }
    @Override
    public String placeOrder() {
        return "Order is Placed";
    }

}
public class Runner {
    public static void main(String[] args) {
        System.setProperty("env", "Rehearsal");
        OrderComponent orderComponent = new OrderComponentProxy();
        System.out.println(orderComponent.placeOrder());
    }
}

Strategy Design Pattern

  • Create an interface

  • Implement different classes

  • Create a class which uses this interface. Pass Interface as method parameter instead of class

  • Create a runner class. Pass implementations to target method

public interface PaymentStrategy {
    public void pay(int amount);
}
public class CreditCardStrategy implements PaymentStrategy{
    @Override
    public void pay(int amount) {
        System.out.println(amount +" paid with credit/debit card");
    }
}

public class PaypalStrategy implements PaymentStrategy{
    @Override
    public void pay(int amount) {
        System.out.println(amount + " paid using Paypal.");
    }
}

public class ShoppingCart {
    public void pay(PaymentStrategy paymentMethod){
        int amount = calculateTotal();
        paymentMethod.pay(amount);
    }
}

 public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();
        cart.pay(new PaypalStrategy("myemail@example.com", "mypwd"));
        cart.pay(new CreditCardStrategy("Pankaj Kumar", "1234567890123456", "786", "12/15"))
 }