Proxy Design Pattern

Proxy Design Pattern is one of the Structural Design Patterns. it provides a wrapper over the original object and is used when the complexity needs to be hidden from the user, it is also called a Surrogate Pattern or Placeholder Pattern.
its focuses on access control and adds an additional security layer around the original object. it governs lazy loading unless it's really required to create an object.

Proxy Design Pattern adds an intermediate Layer by creating proxy classes to perform certain operations and add as well if required, and hide all these complexities from the user

Some real-world examples of Proxy Design Patterns are

a company has a list of block sites saved on the Router server. the employee will hit the youtube end point from the browser, but the network call will go through the router first, it will check the URL and only allow it if it's not in the blocked list else will return an error.

a function is making a call to an API that charges an amount for each call, hence before actually calling the API, the proxy layer will check all the required values so that the client won't make invalid calls which will cost money.

The proxy design pattern is used when processing is very complex but required to provide a simplified version.

based on requirements, proxy design patterns are divided into 4 categories

  • Remote Proxy

An object is received from the network, but it's very expensive and time-consuming to deserialize and create the object back from streams, hence a local proxy object will be created and the network object will only be created when absolutely required.

  • virtual proxy

Hibernate has load() and get() methods to retrieve records from the database. get() method loads the data from the database but the load() method doesn't go to the database directly, instead, it returns a proxy object and only goes to the database when the data is actually required. this is also called a Virtual Proxy and is mainly used as a proxy placeholder for heavy and expensive-to-create objects.

  • Protection proxy

a user is making calls to a secure method. but it requires a lot of access checks if authorized then only will call the actual secure method else refuse the call. it allows to addition additional security over existing calls. this is also called a Protection Proxy.

  • smart proxy

some times a call requires some common data before actually making the calls, to simplify the flow for the user, take the required parameters only and fill all the common data in the proxy method or in other words do all the clericals work before making the calls. this is also called a Smart Proxy.

package org.wesome.design.patterns;

public interface Fruit {
    Apple processFruit(Apple apple);
}
package org.wesome.design.patterns;

import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
public class Apple {
    private String name;
    private String taste;
    private String tagLine;
}
package org.wesome.design.patterns;

public class AppleFruitImpl implements Fruit {
    @Override
    public Apple processFruit(Apple apple) {
        /*  process the fruit here  */
        apple.setTagLine("i love " + apple.getName() + " apple its very " + apple.getTaste());
        return apple;
    }
}
package org.wesome.design.patterns;

import java.util.Objects;

public class ProxyFruitImpl extends AppleFruitImpl {
    @Override
    public Apple processFruit(Apple apple) {
        /*  process some additional checks then delegate work to actual class    */
        if (Objects.isNull(apple)) {
            System.out.println("since apple object is null, it cannot be processed");
            return null;
        }

        if (Objects.isNull(apple.getName())) {
            System.out.println("Apple name is missing, adding name");
            apple.setName("Default Apple");
        }

        if (Objects.isNull(apple.getTaste())) {
            System.out.println("Apple taste is missing, adding taste");
            apple.setTaste("Default Taste");
        }

        Apple processFruit = super.processFruit(apple);
        return processFruit;
    }
}
package org.wesome.design.patterns;

public class Proxy {
    public static void main(String[] args) {
        Fruit fruit = new ProxyFruitImpl();

        Apple processFruit = fruit.processFruit(null);
        System.out.println(processFruit);

        Apple apple = new Apple(null, "Sweet", null);
        processFruit = fruit.processFruit(apple);
        System.out.println(processFruit);

        apple = new Apple("Macintosh", null, null);
        processFruit = fruit.processFruit(apple);
        System.out.println(processFruit);

        apple = new Apple("Macintosh", "Sweet", null);
        processFruit = fruit.processFruit(apple);
        System.out.println(processFruit);
    }
}
package org.wesome.design.patterns;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class ProxyTest {
    @Test
    void testNullApple() {
        Fruit fruit = new ProxyFruitImpl();
        Apple dummyApple = fruit.processFruit(null);
        Assertions.assertAll(() -> Assertions.assertNull(dummyApple));
    }

    @Test
    void testDefaultAppleName() {
        Fruit fruit = new ProxyFruitImpl();
        Apple apple = new Apple(null, "Sweet", null);
        Apple dummyApple = fruit.processFruit(apple);
        Assertions.assertAll(() -> {
            Assertions.assertNotNull(dummyApple);
            Assertions.assertNotNull(dummyApple.getName());
            Assertions.assertEquals("Default Apple", dummyApple.getName());
            Assertions.assertNotNull(dummyApple.getTaste());
            Assertions.assertEquals("Sweet", dummyApple.getTaste());
            Assertions.assertNotNull(dummyApple.getTagLine());
        });
    }

    @Test
    void testDefaultAppleTaste() {
        Fruit fruit = new ProxyFruitImpl();
        Apple apple = new Apple("Macintosh", null, null);
        Apple dummyApple = fruit.processFruit(apple);
        Assertions.assertAll(() -> {
            Assertions.assertNotNull(dummyApple);
            Assertions.assertNotNull(dummyApple.getName());
            Assertions.assertEquals("Macintosh", dummyApple.getName());
            Assertions.assertNotNull(dummyApple.getTaste());
            Assertions.assertEquals("Default Taste", dummyApple.getTaste());
            Assertions.assertNotNull(dummyApple.getTagLine());
        });
    }

    @Test
    void testMacintoshApple() {
        Fruit fruit = new ProxyFruitImpl();
        Apple apple = new Apple("Macintosh", "Sweet", null);
        Apple macintosh = fruit.processFruit(apple);
        Assertions.assertAll(() -> {
            Assertions.assertNotNull(macintosh);
            Assertions.assertNotNull(macintosh.getName());
            Assertions.assertEquals("Macintosh", macintosh.getName());
            Assertions.assertNotNull(macintosh.getTaste());
            Assertions.assertEquals("Sweet", macintosh.getTaste());
            Assertions.assertNotNull(macintosh.getTagLine());
        });
    }
}

The Proxy Design Pattern is almost equivalent to Decorator Design Pattern but the difference is Decorator Design Pattern focuses on adding additional processing and duties whereas the Proxy Design Pattern focuses on restricting access or only making the calls if really required.

follow us on