Design Patterns (Very simple examples)

Design Patterns (Very simple examples)

In the software industry, most common problems have common solutions, and that solution follows common types of patterns, The answers are called?design patterns.

Many of us encounter this situation where we must deal with object creation, structure management, and behaviour observation of problems. So in order to solve these correctly, we have?design patterns

There are 3 categories into which all the design patterns fall.

No alt text provided for this image

Let’s talk about common design patterns in detail

Creational Design Pattern

1. Singleton Pattern:

The singleton pattern is a design pattern that restricts the instantiation of a class to one object.

When we want to create only one instance of our class’s object we need to use a singleton design pattern.

In this, we create only 1 instance of our class throughout the lifecycle of our application

Such as we need to create a DB connection, some config, 3rd party APIs, etc.

Let’s see a simple example of a singleton design pattern.

public class Singleton {
    //This is the variable where we will store instance
    // This is static because we want only one instance
private static Singleton instance = null;
//This is private class because we dont want someone to create //direct object of this class
private Singleton() {
        System.out.println("new object created of Singleton()");
    }
//This is the factory method by which someone can access the //object
public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}        

Now let’s see if the above class is singleton or not,

1. Create a duplicate object by using reflection:

Singleton obj1= Singleton.getInstance();
Class clazz = Class.forName("com.package.Singleton");
Constructor<Singleton> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);Singleton obj2= constructor.newInstance();System.out.println("HashCode of obj1: " + obj1.hashCode());   
System.out.println("HashCode of obj2: " + obj2.hashCode());        

2. Create a duplicate object by using?Serialization:

Singleton obj1= Singleton.getInstance();
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("loction/save"));
outputStream.writeObject(singletone);
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("loction/save"));
Singleton obj2 = (Singleton) objectInputStream.readObject();System.out.println("HashCode of obj1: " + obj1.hashCode());   
System.out.println("HashCode of obj2: " + obj2.hashCode());        

3.?Create an object by Cloning the object:

Singleton obj1 = Singleton.getInstance();SingletonDesign obj2 = (Singleton) obj1.clone();
System.out.println("HashCode of obj1: " + obj1.hashCode());   
System.out.println("HashCode of obj2: " + obj2.hashCode());        

4. Create an object by Thread:

ExecutorService service = Executors.newFixedThreadPool(2);
service.submit(SingletonTest::useSingleton);
service.submit(SingletonTest::useSingleton);
service.shutdown();
static void useSingleton(){
    Singleton singleton = Singleton.getInstance();
    System.out.println("HashCode of obj: " + obj.hashCode());
}        

Now let’s try to create a fully secure singleton

public class Singleton {

private static volatile Singleton mInstance;
private Singleton() {
        // To prevent reflection we need to do not null check
        if (mInstance != null) {
            throw new RuntimeException("Please use getInstance()");
        }
        System.out.println("Singleton Constructor");
    }
    public final static Singleton getInstance() {
        // Make thread-safe Singleton by using Double Check Locking
        if (mInstance == null) {
            // 1- check
            synchronized (Singleton.class) {
                if (mInstance == null) {
                    // 2- check
                    mInstance = new Singleton();
                }
            }
        }
        return mInstance;
    }
    //Prevent from Serialization
    protected Object readResolve() throws ObjectStreamException {
        //return the mInstance or throw the Exception
        return mInstance;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        // instead of super.clone() return the mInstance or throw the Exception
        return mInstance;
    }
}        

2. Builder Design Pattern:

Builder design pattern says to create a complex object step by step and the final step will return the object.

The process of constructing an object should be generic so that it can be used to create different representations of the same object.

If we don’t want to give all the values required we can leave it, By default value will be assigned to it.

Let's see the example

Take an example of phone class A Phone can have these 4 properties which we provide in the constructor

public class Phone {

    private String name;
    private String brand;
    private int prize;
    private int ram;
    public Phone(String name, String brand, int prize, int ram) {
        this.name = name;
        this.brand = brand;
        this.prize = prize;
        this.ram = ram;
    }
    @Override
    public String toString() {
        return "Phone{" +
                "name='" + name + '\'' +
                ", brand='" + brand + '\'' +
                ", prize=" + prize +
                ", ram=" + ram +
                '}';
    }
}        

We want to create a phone by using a builder pattern so we need to have PhoneBuilder so that we can provide the values required value to it

public class PhoneBuilder {

    private String name;
    private String brand;
    private int prize;
    private int ram;
    public String getName() {
        return name;
    }
    public PhoneBuilder setName(String name) {
        this.name = name;
        return this;
    }
    public String getBrand() {
        return brand;
    }
    public PhoneBuilder setBrand(String brand) {
        this.brand = brand;
        return this;
    }
    public int getPrize() {
        return prize;
    }
    public PhoneBuilder setPrize(int prize) {
        this.prize = prize;
        return this;
    }
    public int getRam() {
        return ram;
    }
    public PhoneBuilder setRam(int ram) {
        this.ram = ram;
        return this;
    }
    public Phone getPhone() {
        return new Phone(name, brand, prize, ram);
    }
}        

In the above phone builder, we have a setter of each method that will return the same object after initializing the value to the property.

public class PhoneConsumer {

    public static void main(String[] args) {
        Phone phone = new PhoneBuilder().setBrand("iPhone")
                .setName("Apple").getPhone();
        System.out.println(phone);
    }
}        

Here we have the PhoneConsumer class which will provide values to the PhoneBuider according to their requirements.

3. Factory Design Pattern:

In the Factory pattern, we create objects without exposing the creation logic to the client and refer to newly created objects using a common interface.

The factory design pattern says that define an interface ( A java interface or an abstract class) and let the subclasses decide which object to instantiate.

Example:

Let’s say we want to create a shape of type Circle or Ractangal or Triangle, We don't want to hardcode the implementation of all shapes so let's say we want to create a shape of type Circle or Triangle or Rectangle.

In order to create a shape by factory method, we need the following things

  1. Interface in which factory method will be there.
  2. Classes of what all types of shapes we want to create.
  3. A Factory will be responsible for creating the object of shape.
  4. The consumer will decide which type of shape is required.

This is the Shape Interface

public interface Shape {
    void draw();
}        

Here we have 2 classes that will implement the Shape Interface and provide the implementation to it.

Circle class:

public class Circle implements Shape{

    @Override
    public void draw() {
        System.out.println("Circle");
    }
}        

Rectangle Class:

public class Rectangle implements Shape{

    @Override
    public void draw() {
        System.out.println("Rectangle");
    }
}        

Now we need a shape factory that will have the object creation logic

public class ShapeFactory {

    public Shape getShape(String type) {
        if (type.equalsIgnoreCase("rectangle")) {
            return new Rectangle();
        } else if (type.equalsIgnoreCase("circle")) {
            return new Circle();
        }
        return null;
    }
}        

Now we need a Shape Consumer

public class ShapeConsumer {

    public static void main(String[] args) {
        ShapeFactory shapeFactory = new ShapeFactory();
        Shape s = shapeFactory.getShape("Circle");
        s.draw();
    }
}        

4. Abstract Factory Design Pattern:

This is similar to the factory pattern but it increases 1 more layer of abstraction in object creation.

Abstract Factory patterns work around a super-factory which creates other factories.

Let's see the example:

Here we have 2 interfaces

Color Interface:

public interface Color {
    void fill();
}        

Shape Interface:

public interface Shape {
    void draw();
}        

Abstract factory:

public abstract class AbstractFactory {
    abstract Color getColor(String colorType);
    abstract Shape getShape(String shapeType);
}        

Color Factory:

From the?Color?factory we will return null for?Shape

public class ColorFactory extends AbstractFactory {

    @Override
    public Color getColor(String colorType) {
        if (colorType.equalsIgnoreCase("red")) {
            return new Red();
        } else if (colorType.equalsIgnoreCase("black")) {
            return new Black();
        }
        return null;
    }
    @Override
    public Shape getShape(String shapeType) {
        return null;
    }
}        

Shape Factory:

From the?Shape?factory, we will return null for?Color

public class ShapeFactory extends AbstractFactory {

    @Override
    public Color getColor(String colorType) {
        return null;
    }
    @Override
    public Shape getShape(String shapeType) {
        if (shapeType.equalsIgnoreCase("ractangle")) {
            return new Ractangle();
        } else if (shapeType.equalsIgnoreCase("circle")) {
            return new Circle();
        }
        return null;
    }
}        

Now we will need a?factory producer?which will return the factory of the type given by the user(Color or Shape)

public class FactoryProducer {

    public static AbstractFactory getFactory(String type) {
        if (type.equalsIgnoreCase("Shape")) {
            return new ShapeFactory();
        } else if (type.equalsIgnoreCase("Color")) {
            return new ColorFactory();
        }
        return null;
    }
}        

Now we need a consumer

public class ShapeAndColorConsumer {

    public static void main(String[] args) {
        AbstractFactory abstractFactory=FactoryProducer.getFactory("Color");
        Color color=abstractFactory.getColor("red");
        color.fill();
    }
}        

Structural Pattern

1. Facade Pattern:

It hides the complexities of the system and provides an interface to the client from where the client can access the system.

In the Facade pattern, there will be a layer that will have the system information and we communicate from this layer.

Let's take an example

You go to the mobile shop and you ask for the Samsung price Shopkeeper will give you the information which he will have

Same for the other phone as well let's say iPhone

Here the layer is Shopkeeper from which we are getting the information about the products.

See in action:

There is an interface?Phone?which has 2 methods

public interface Phone {
    void modelName();
    void modelPrice();
}        

There are 2 classes currently(Can be more) that are implementing this interface

1 is?iPhone

public class Iphone implements Phone {

    @Override
    public void modelName() {
        System.out.println("Iphone");
    }
    @Override
    public void modelPrice() {
        System.out.println("6000");
    }
}        

2nd is?Samsung

public class Samsung implements Phone {

    @Override
    public void modelName() {
        System.out.println("Samsung");
    }
    @Override
    public void modelPrice() {
        System.out.println("1000");
    }
}        

Here will be a shopkeeper who will have all the information about the products

public class ShopKeeper {
    Iphone iphone;
    Samsung samsung;
    public ShopKeeper() {
        this.iphone = new Iphone();
        this.samsung = new Samsung();
    }
    public void getIphoneDetail(){
        iphone.modelName();
        iphone.modelPrice();
    }
    public void getSamsungDetail(){
        samsung.modelName();
        samsung.modelPrice();
    }
}        

Customers want information about products from?shopkeepers

public class Customer {

    public static void main(String[] args) {
        ShopKeeper shopKeeper = new ShopKeeper();
        shopKeeper.getIphoneDetail();
    }
}        

2. Composite Pattern:

Composite Pattern says to structure objects in such a way as a tree where common properties can be used from top to bottom.

In a company, all the employees will have some common structures like

empId, salary, Department. etc

Let's see the example:

We will have an?Employee?class where we have a common structure of A Employee

public interface Employee {
    String getName();
    int getId();
    String getDepartment();
}        

Now we can have?HRs

public class HRs implements Employee {

    private String name;
    private int id;
    private String department;

    public HRs(String name, int id, String department) {
        this.name = name;
        this.id = id;
        this.department = department;
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public int getId() {
        return this.id;
    }

    @Override
    public String getDepartment() {
        return this.department;
    }

    @Override
    public String toString() {
        return "HRs{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", department='" + department + '\'' +
                '}';
    }
}        

We can have the Engineers

public class Engineer implements Employee {

    private String name;
    private int id;
    private String department;

    public Engineer(String name, int id, String department) {
        this.name = name;
        this.id = id;
        this.department = department;
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public int getId() {
        return this.id;
    }

    @Override
    public String getDepartment() {
        return this.department;
    }

    @Override
    public String toString() {
        return "Engineer{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", department='" + department + '\'' +
                '}';
    }

}        

We can have the salesPerson

public class SalesMan implements Employee {

    private String name;
    private int id;
    private String department;

    public SalesMan(String name, int id, String department) {
        this.name = name;
        this.id = id;
        this.department = department;
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public int getId() {
        return this.id;
    }

    @Override
    public String getDepartment() {
        return this.department;
    }

    @Override
    public String toString() {
        return "SalesMan{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", department='" + department + '\'' +
                '}';
    }
}        

Now in the Company, we have a list of employees as below:

public class Company {

    public static void main(String[] args) {
        List<Employee> employees = new ArrayList();
        Employee eng = new Engineer("Aalishan", 1, "Tech");
        Employee sales = new SalesMan("Santosh", 2, "Sale");
        Employee hr = new HRs("Abc", 3, "Hr");
        employees.add(eng);
        employees.add(sales);
        employees.add(hr);
        for (Employee e : employees
        ) {
            System.out.println(e.toString());
        }
    }

}        

Behavioural Pattern

  1. Observer pattern:

Sometimes we want to receive a notification when some event happens someplace. so for that, we need to register ourselves to get notified, In simple words, we want to observe some data at some event occurrence.

Suppose we are building a cricket app that notifies viewers about information such as current score, run rate, etc. Suppose we have made two display elements CurrentScoreDisplay and AverageScoreDisplay. CricketData has all the data (runs, bowls, etc.), and whenever data changes the display elements are notified with new data and they display the latest data accordingly.

Let's take an example of the Observer pattern

We have a?Subject?that will have:

public interface Subject {
    void register(Observer observer);
    void unRegister(Observer observer);
    void notifyMessage(Message message);
}        

We have?Observer?Interface

public interface Observer {
    void updateMessage(Message message);
}        

We have?Message?to publish

public class Message {

    private final String message;

    Message(String msg) {
        message = msg;
    }

    public String getMessage() {
        return message;
    }
}        

We have a?MessagePublisher

public class MessagePublisher implements Subject {

    // Collection of observers
    private Set<Observer> observers = new HashSet<>();

    @Override
    public void register(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void unRegister(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyMessage(Message message) {
        for (Observer one : observers
        ) {
            one.updateMessage(message);
        }
    }

}        

Now 1st?Subscriber

public class FirstSubscriber implements Observer {

    @Override
    public void updateMessage(Message message) {
        System.out.println(message.getMessage());
    }

}        

Now 2nd?Subscriber

public class SecondSubscriber implements Observer {

    @Override
    public void updateMessage(Message message) {
        System.out.println(message.getMessage());
    }

}        

Now we need a test class which will have:

public class ObserverTest {

    public static void main(String[] args) {
        FirstSubscriber firstMessageSubscriber = new FirstSubscriber();
        SecondSubscriber secondMessageSubscriber = new SecondSubscriber();
        ThirdSubscriber thirdMessageSubscriber = new ThirdSubscriber();
        //Creating Message Publisher
        MessagePublisher messagePublisher = new MessagePublisher();
        //Register two Subscribers to get notifications on any update
        messagePublisher.register(firstMessageSubscriber);
        messagePublisher.register(secondMessageSubscriber);
        messagePublisher.register(thirdMessageSubscriber);
        //firstMessageSubscriber and secondMessageSubscriber will receive the update
        messagePublisher.notifyMessage(new Message("This is First Message"));
        System.out.println("---------------------------------------------------------------");
    }

}        

============================================================

Dipen Jansari

SD3 - Android at FloBiz | Ex-ZebPay | Ex-OpenxCell | Building myBillBook at FloBiz

1 å¹´

Insightful

要查看或添加评论,请登录

Aalishan Ansari的更多文章

  • Android Activity Frequently Asked Interview Questions Part-1

    Android Activity Frequently Asked Interview Questions Part-1

    1. What is the activity?/What do you know about the activity? Ans: Activity is one of the building blocks of Android…

    6 条评论
  • MVP Architecture with Example in Android

    MVP Architecture with Example in Android

    In this blog, we will take an example of a login screen in Android and cover up MVP very simply. As shown in the above…

    2 条评论
  • Activity lifecycle callbacks A →B and B →A

    Activity lifecycle callbacks A →B and B →A

    Suppose you are having one more activity called C If you kill Activity C from the task manager so onDestroy(C)…

    1 条评论
  • Fragment to Fragment Communication Using ViewModel

    Fragment to Fragment Communication Using ViewModel

    In MainActivity class MainActivity : AppCompatActivity() { private var _activityMainBinding: ActivityMainBinding?…

    2 条评论
  • S.O.L.I.D Principles (Examples in Kotlin)

    S.O.L.I.D Principles (Examples in Kotlin)

    Lots of developers do the code, but A good developer is one who takes care of the code with respect to many programming…

    1 条评论
  • References Role in Garbage Collection

    References Role in Garbage Collection

    In Java, there are four types of references differentiated by the way by which they are garbage collected. Strong…

  • Activity|LaunchMode|With Lifecycle

    Activity|LaunchMode|With Lifecycle

    Before starting development in any platform first, we should understand the ecosystem of the platform. If you are…

    2 条评论
  • OOPs Concepts (Examples in Kotlin)

    OOPs Concepts (Examples in Kotlin)

    Object-Oriented Programming in detail. The Oops concepts are the foundation when you want to create a good application…

  • Rx Android

    Rx Android

    Rx Android is a concept used in android app development to give app better performance. Reactive Extension is a library…

    1 条评论
  • Context/Application Context?

    Context/Application Context?

    Context is the bridge between components. You use it to communicate between components, instantiate components, and…

    1 条评论

社区洞察

其他会员也浏览了