Design Pattern Proxy, in a nutshell

Luiz Gustavo De O. Costa
6 min readAug 8, 2021

--

A long time ago in a city far, far away…. 👾 well in fact was in Araraquara, Brazil 🇧🇷. Due to a connection speed we faced some problems to make the application start up properly (timeout), then a team mate, Eduardo had an idea to build a proxy[3] to solve the problem, from this day forward I would know of the existence of this pattern.

Proxy[5]

1. Which problem this pattern intent to solve?

Based on the GOF book “Provide a surrogate or placeholder for another object to control access to it.[1]”.

Sounds abstract, doesn’t it? 😟

Let’s see the definition by Head First book “A remote proxy acts as a local representative to a remote object. What’s a “remote object”? It’s an object that lives in the heap of a different Java Virtual Machine (or more generally, a remote object that is running in a different address space). What’s a “local representative”? It’s an object that you can call local methods on and have them forwarded on to the remote object.[2]

2. Proxy flavours

  • Virtual Proxies: delaying the creation and initialisation of expensive objects until needed, where the objects are created on demand (For example creating the RealSubject object only when the doSomething method is invoked).[4]
  • Remote Proxies: providing a local representation for an object that is in a different address space. A common example is Java RMI stub objects. The stub object acts as a proxy where invoking methods on the stub would cause the stub to communicate and invoke methods on a remote object (called skeleton) found on a different machine.[4]
  • Protection Proxies: where a proxy controls access to RealSubject methods, by giving access to some objects while denying access to others.[4]
  • Smart References (Proxy): providing a sophisticated access to certain objects such as tracking the number of references to an object and denying access if a certain number is reached, as well as loading an object from database into memory on demand.[4]

2. Participants

Subject → Defines the common interface for RealSubject and Proxy so that a Proxy can be used anywhere a RealSubject is expected. [1]

RealSubject → Defines the real object that the proxy represents.[1]

Proxy → Maintains a reference that lets the proxy access the real subject. Proxy may refer to a Subject if the RealSubject and Subject interfaces are the same[1], provides an interface identical to Subject’s so that a proxy can by substituted for the real subject.[1]

3. Diagrams

This section shows this pattern as a UML diagram.

3.1. GOF

GOF Proxy diagram

3.2. Head First

The examples below are based on the Head First Design Patterns book.

3.2.1. Java proxy

Head First Java Proxy

3.2.2. Virtual proxy

Head First Virtual Proxy

4. Hands-on

The scope of this hands-on will be Protection Proxie (where a proxy controls access to RealSubject methods, by giving access to some objects while denying access to others.[4]).

Our RealSubject is AccountRealSubject and the proxy is the AccountProxy.

As you notice this example will handle the Account use case. Since this kind of use case is really huge, I pick only the piece of open and close an account, without all business rules, see below how the classes are organised.

Hands-on Proxy

4.1. Tests

All tests are available on GitHub[7].

4.1.1. Proxy

@DisplayName("Proxy test")
@ExtendWith(MockitoExtension.class)
class AccountProxyTest {

AccountProxy proxy;
AccountService realSubject;

@BeforeEach
void setUp(@Mock AccountRealSubject accountRealSubject) {
this.realSubject = accountRealSubject;
proxy = new AccountProxy(accountRealSubject);
}

@Test
void open() {
String[] owners = new String[] {"Cleveland Brow", "Cleveland Junior"};
proxy.open(owners);
verify(realSubject, times(1)).open(owners);
}

@Test
void close() {
var iban = "My marvelous IBAN";
proxy.close(iban);
verify(realSubject, never()).open(iban);
verify(realSubject, times(1)).close(iban);
}
}

4.1.1. RealSubject

@ExtendWith(MockitoExtension.class)
@DisplayName("Real subject")
class AccountRealSubjectTest implements WithAssertions {

AccountRealSubject account;
AccountRepository repository;

@BeforeEach
void setUp(@Mock AccountRepository repository) {
this.repository = repository;
this.account = new AccountRealSubject(repository);
}

@Test
void open() {
String[] owners = new String[]{"Clark Kent", "Kent Beck"};
account.open(owners);
verify(repository, times(1)).save(owners);
}

@Test
void close() {
var iban = "TUS";
account.close(iban);
verify(repository, times(1)).close(iban);
}
}

4.2. Code

4.2.1. Client

package com.gof.structural.proxy;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j
public class AppClient {

private final AccountService service;

public AppClient(AccountService service) {
this.service = service;
}

public static void main(String[] args) throws InterruptedException {
// In a real scenario this should be provide by @Inject or @Autowired
AppClient client = new AppClient(new AccountProxy(new AccountRealSubject(InMemory.INSTANCE)));

log.info("Let's open a new account for Luiz and Louis");
var iban = client.openDigitalAccount("Luiz", "Louis");

log.info("Time to save money....");
TimeUnit.SECONDS.sleep(5);

if (client.closeDigitalAccount(iban)) {
log.info("Now, the account is closed");
}

}

private String openDigitalAccount(String... owners) {
return service.open(owners);
}

private boolean closeDigitalAccount(String iban) {
return service.close(iban);
}
}

4.2.2 AccountProxy

package com.gof.structural.proxy;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class AccountProxy implements AccountService {

private AccountRealSubject account;

public AccountProxy(AccountRealSubject account) {
this.account = account;
}

@Override
public String open(String... owners) {
log.info("Proxy is a proxy, needs to request the operation open to real subject");
return account.open(owners);
}

@Override
public boolean close(String iban) {
log.info("Proxy is a proxy, needs to request the operation close to real subject");
return account.close(iban);
}
}

4.2.3 AccountRealSubject

package com.gof.structural.proxy;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class AccountRealSubject implements AccountService {

private final AccountRepository repository;

public AccountRealSubject(AccountRepository repository) {
this.repository = repository;
}

@Override
public String open(String... owners) {
log.info("Hi there! I'm real subject, let's create an account for you");
return repository.save(owners);
}

@Override
public boolean close(String iban) {
log.info("Hi there! I'm real subject, let's close the account for you");
return repository.close(iban);
}
}

4.2.4. InMemory (Repository)

package com.gof.structural.proxy;

import lombok.extern.slf4j.Slf4j;

import java.time.ZonedDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static java.util.Objects.nonNull;

@Slf4j
class InMemory implements AccountRepository {

protected static final InMemory INSTANCE;

static {
INSTANCE = new InMemory(new HashMap<>());
}

private final Map<String, AccountDetails> accounts;

private InMemory(Map<String, AccountDetails> accounts) {
this.accounts = accounts;
}

public String save(String... owners) {
var iban = UUID.randomUUID().toString();
log.info("New iban created {}", iban);
var accountDetails = AccountDetails.builder()
.owners(List.of(owners))
.iban(iban)
.active(true)
.memberSince(ZonedDateTime.now())
.build();
accounts.put(iban, accountDetails);
return iban;
}

public boolean close(String iban) {
log.info("The account {} will be closed.", iban);
return nonNull(accounts.remove(iban));
}

public List<AccountDetails> findAll() {
return accounts.values().stream().collect(Collectors.toUnmodifiableList());
}

public Optional<AccountDetails> findById(String id) {
return Optional.ofNullable(accounts.get(id));
}

}

6. Code

The repository is available on GitHub.

7. Run it locally

To run it locally, you will need to follow the steps below, using IntelliJ or any other IDEA.

  • Clone the repo using the command
git clone https://github.com/luizgustavocosta/design-patterns-in-java
  • Then navigate to the folder
design-patterns-in-java/gang-of-four/src/main/java/com/gof/structural/proxy
  • And run the main class AppClient.java

8. Tech stack

  • OpenJDK11
  • JUnit5
  • AssertJ
  • Mockito
  • Lombok
  • Git and GitHub

9. Conclusion

This pattern is really useful when the cost of creating another object isn’t easy. Although looking back, this pattern was widely used and is used nowadays, either for WebServices or for Spring AOP.

10. References

[1] — Gamma Erich, Helm Richard, Johnson Ralph, Vlissides John, Grady Booch. (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley.

[2]— Eric Freeman, Elisabeth Robson. (2020). Head First Design Patterns, 2nd Edition. O’Reilly Media, Inc.

[3] — https://code.google.com/archive/p/dirtyproxy/

[4 ]— https://www.oodesign.com/proxy-pattern.html

[5] — https://pixabay.com/users/edwebster-17053697/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=5301801

[6] — https://springframework.guru/gang-of-four-design-patterns/proxy-pattern/

[7] — https://github.com/luizgustavocosta/design-patterns-in-java/tree/master/gang-of-four/src/main/java/com/gof/structural/proxy

[8] — https://refactoring.guru/design-patterns/proxy/java/example

— — — END — — —

--

--

Luiz Gustavo De O. Costa
Luiz Gustavo De O. Costa

Written by Luiz Gustavo De O. Costa

Hey friend!! I’m Luiz Gustavo, a Java developer and I’m here to learn and write about Java, tests and good practices

No responses yet