Proxy Pattern

1. Giới Thiệu

1.1 Định Nghĩa

Proxy Pattern là một structural pattern cung cấp một surrogate hoặc placeholder cho một đối tượng khác để kiểm soát truy cập đến đối tượng đó. Proxy đóng vai trò trung gian, cho phép thực hiện một số tác vụ trước hoặc sau khi truy cập đối tượng thực.

1.2 Mục Đích

  • Kiểm soát truy cập đến đối tượng
  • Thêm functionality trước/sau khi truy cập
  • Lazy loading của đối tượng nặng
  • Caching kết quả
  • Logging và monitoring

2. Cấu Trúc Cơ Bản

2.1 Image Loading Example

// Subject interface
public interface Image {
    void display();
}

// Real Subject
public class RealImage implements Image {
    private String filename;

    public RealImage(String filename) {
        this.filename = filename;
        loadFromDisk();
    }

    private void loadFromDisk() {
        System.out.println("Loading " + filename);
    }

    @Override
    public void display() {
        System.out.println("Displaying " + filename);
    }
}

// Proxy
public class ProxyImage implements Image {
    private RealImage realImage;
    private String filename;

    public ProxyImage(String filename) {
        this.filename = filename;
    }

    @Override
    public void display() {
        if (realImage == null) {
            realImage = new RealImage(filename);
        }
        realImage.display();
    }
}

// Client
public class Client {
    public static void main(String[] args) {
        Image image = new ProxyImage("test.jpg");

        // Image will be loaded from disk
        image.display();

        // Image will not be loaded from disk
        image.display();
    }
}

3. Ví Dụ Thực Tế

3.1 Database Connection Proxy

// Subject interface
public interface DatabaseConnection {
    void connect();
    void executeQuery(String query);
    void disconnect();
}

// Real Subject
public class RealDatabaseConnection 
        implements DatabaseConnection {
    private Connection connection;

    @Override
    public void connect() {
        // Actual database connection
        System.out.println("Connected to database");
    }

    @Override
    public void executeQuery(String query) {
        System.out.println("Executing query: " + query);
    }

    @Override
    public void disconnect() {
        System.out.println("Disconnected from database");
    }
}

// Proxy
public class DatabaseConnectionProxy 
        implements DatabaseConnection {
    private RealDatabaseConnection realConnection;
    private final String username;
    private final String password;

    public DatabaseConnectionProxy(
            String username, String password) {
        this.username = username;
        this.password = password;
    }

    @Override
    public void connect() {
        if (realConnection == null) {
            if (authenticate()) {
                realConnection = 
                    new RealDatabaseConnection();
                realConnection.connect();
            } else {
                throw new SecurityException(
                    "Authentication failed");
            }
        }
    }

    @Override
    public void executeQuery(String query) {
        if (realConnection == null) {
            connect();
        }
        logQuery(query);
        realConnection.executeQuery(query);
    }

    @Override
    public void disconnect() {
        if (realConnection != null) {
            realConnection.disconnect();
        }
    }

    private boolean authenticate() {
        // Authentication logic
        return true;
    }

    private void logQuery(String query) {
        System.out.println("Logging query: " + query);
    }
}

3.2 Caching Proxy

// Subject interface
public interface ExpensiveObject {
    List<String> process(String input);
}

// Real Subject
public class RealExpensiveObject 
        implements ExpensiveObject {
    @Override
    public List<String> process(String input) {
        // Expensive processing
        try {
            Thread.sleep(2000); // Simulate work
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return Arrays.asList(
            input.split(" "));
    }
}

// Proxy
public class CachingProxy implements ExpensiveObject {
    private RealExpensiveObject realObject;
    private Map<String, List<String>> cache;

    public CachingProxy() {
        this.realObject = new RealExpensiveObject();
        this.cache = new ConcurrentHashMap<>();
    }

    @Override
    public List<String> process(String input) {
        return cache.computeIfAbsent(
            input, k -> realObject.process(k));
    }

    public void clearCache() {
        cache.clear();
    }

    public int getCacheSize() {
        return cache.size();
    }
}

4. Spring Framework Integration

4.1 AOP Proxy Example

// Aspect
@Aspect
@Component
public class LoggingAspect {
    private static final Logger logger = 
        LoggerFactory.getLogger(LoggingAspect.class);

    @Around("@annotation(Loggable)")
    public Object logMethod(
            ProceedingJoinPoint joinPoint) 
            throws Throwable {
        String methodName = joinPoint.getSignature()
            .getName();

        logger.info("Starting {}", methodName);
        long start = System.currentTimeMillis();

        Object result = joinPoint.proceed();

        long executionTime = System.currentTimeMillis() - 
            start;
        logger.info("{} completed in {}ms", 
            methodName, executionTime);

        return result;
    }
}

// Annotation
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable {
}

// Service
@Service
public class UserService {
    @Loggable
    public User findById(Long id) {
        // Business logic
        return new User(id);
    }
}

4.2 Remote Proxy

// Remote interface
public interface RemoteService {
    String getData(String key);
}

// Remote implementation
@Service
public class RemoteServiceImpl implements RemoteService {
    @Override
    public String getData(String key) {
        // Actual remote call
        return "Data for " + key;
    }
}

// Proxy
@Service
public class RemoteServiceProxy implements RemoteService {
    private final RemoteService remoteService;
    private final Cache<String, String> cache;

    public RemoteServiceProxy(RemoteService remoteService) {
        this.remoteService = remoteService;
        this.cache = Caffeine.newBuilder()
            .expireAfterWrite(Duration.ofMinutes(10))
            .maximumSize(100)
            .build();
    }

    @Override
    public String getData(String key) {
        return cache.get(key, k -> {
            try {
                return remoteService.getData(k);
            } catch (Exception e) {
                throw new RuntimeException(
                    "Failed to get data", e);
            }
        });
    }
}

5. Testing Proxy Pattern

5.1 Unit Testing

@ExtendWith(MockitoExtension.class)
class DatabaseConnectionProxyTest {
    @Mock
    private RealDatabaseConnection realConnection;

    @InjectMocks
    private DatabaseConnectionProxy proxy;

    @Test
    void whenValidCredentials_thenConnect() {
        // When
        proxy.connect();

        // Then
        verify(realConnection).connect();
    }

    @Test
    void whenInvalidCredentials_thenThrowException() {
        // Given
        DatabaseConnectionProxy proxy = 
            new DatabaseConnectionProxy(
                "invalid", "invalid");

        // When/Then
        assertThrows(SecurityException.class, 
            () -> proxy.connect());
    }
}

5.2 Integration Testing

@SpringBootTest
class RemoteServiceProxyTest {
    @Autowired
    private RemoteServiceProxy proxy;

    @Test
    void whenCached_thenFasterResponse() {
        // First call - cache miss
        long start = System.currentTimeMillis();
        String result1 = proxy.getData("test");
        long firstCallTime = System.currentTimeMillis() - 
            start;

        // Second call - cache hit
        start = System.currentTimeMillis();
        String result2 = proxy.getData("test");
        long secondCallTime = System.currentTimeMillis() - 
            start;

        assertEquals(result1, result2);
        assertTrue(secondCallTime < firstCallTime);
    }
}

6. Lợi Ích và Nhược Điểm

6.1 Lợi Ích

  1. Kiểm soát truy cập tốt hơn
  2. Lazy loading và caching
  3. Logging và monitoring dễ dàng
  4. Tách biệt logic xử lý
  5. Bảo mật và validation

6.2 Nhược Điểm

  1. Tăng độ phức tạp của code
  2. Có thể ảnh hưởng đến hiệu suất
  3. Khó debug khi có nhiều proxy
  4. Có thể vi phạm Single Responsibility Principle

7. Best Practices

7.1 Virtual Proxy

public class VirtualProxyImage implements Image {
    private volatile RealImage realImage;
    private final String filename;
    private final Object lock = new Object();

    public VirtualProxyImage(String filename) {
        this.filename = filename;
    }

    @Override
    public void display() {
        RealImage localRef = realImage;
        if (localRef == null) {
            synchronized (lock) {
                localRef = realImage;
                if (localRef == null) {
                    realImage = localRef = 
                        new RealImage(filename);
                }
            }
        }
        localRef.display();
    }
}

7.2 Protection Proxy

public class SecureDocumentProxy implements Document {
    private RealDocument document;
    private User user;

    public SecureDocumentProxy(
            RealDocument document, User user) {
        this.document = document;
        this.user = user;
    }

    @Override
    public void view() {
        if (hasViewPermission()) {
            document.view();
        } else {
            throw new SecurityException(
                "No view permission");
        }
    }

    @Override
    public void edit() {
        if (hasEditPermission()) {
            document.edit();
        } else {
            throw new SecurityException(
                "No edit permission");
        }
    }

    private boolean hasViewPermission() {
        return user.hasPermission("VIEW");
    }

    private boolean hasEditPermission() {
        return user.hasPermission("EDIT");
    }
}

8. Common Issues và Solutions

8.1 Performance Optimization

public class SmartProxy implements Service {
    private RealService service;
    private LoadBalancer loadBalancer;
    private CircuitBreaker circuitBreaker;

    @Override
    public Response execute(Request request) {
        if (!circuitBreaker.isOpen()) {
            try {
                Server server = loadBalancer
                    .getNextServer();
                return executeWithRetry(
                    request, server);
            } catch (Exception e) {
                circuitBreaker.recordFailure();
                throw e;
            }
        }
        throw new CircuitBreakerException();
    }

    private Response executeWithRetry(
            Request request, Server server) {
        int retries = 3;
        Exception lastException = null;

        while (retries > 0) {
            try {
                return service.execute(request);
            } catch (Exception e) {
                lastException = e;
                retries--;
                if (retries > 0) {
                    Thread.sleep(1000);
                }
            }
        }

        throw new RetryExhaustedException(
            lastException);
    }
}

8.2 Memory Management

public class MemoryAwareProxy implements Resource {
    private WeakReference<RealResource> resourceRef;
    private final ResourceFactory factory;

    public MemoryAwareProxy(ResourceFactory factory) {
        this.factory = factory;
    }

    @Override
    public void use() {
        RealResource resource = null;
        if (resourceRef != null) {
            resource = resourceRef.get();
        }

        if (resource == null) {
            resource = factory.createResource();
            resourceRef = new WeakReference<>(resource);
        }

        resource.use();
    }
}

9. References

9.1 Books

  • Design Patterns: Elements of Reusable Object-Oriented Software
  • Head First Design Patterns
  • Clean Architecture by Robert C. Martin
  • Patterns of Enterprise Application Architecture

9.2 Online Resources