[디자인 패턴] Proxy Pattern과 Decorator Pattern

벼랑 끝 코딩·2025년 3월 21일

Design Pattern

목록 보기
6/6
post-thumbnail

새로운 아이디어와 함께 기능은 나날이 발전되어 간다.
하지만 기능의 발전은 결코 쉬운일이 아니다.
점 위치를 하나 바꾸더라도 시스템 전체의 영향을 미칠 수 있다.
그리고 이러한 현실이 기능 변화에 대해 폐쇄적인 태도를 가지게 한다.

매번 완성된 설계도를 지우고 다시 써가는 위험한 작업을 해야만 할까?
기능을 유연하게 추가하여 부담을 줄이는 방법은 없을까?

Proxy

잘 그린 그림의 일부분을 지우고 수정하는 것은 매우 위험하다.
물감이 번질 수도 있고, 생각했던 것과 다르게 색감이 맞지 않을 수도 있다.
원하는 부분을 덧붙이기만 할 수 있다면 얼마나 좋을까?

그것을 가능하게 만드는 개념이 바로 Proxy다.

Proxy란 다른 객체를 대신하여 대리 역할을 수행하는 객체를 의미한다.
즉 객체의 객체, 객체를 품은 대리인 객체라는 말이다.

대리 역할을 두면 할 수 있는 일이 많아진다.
내가 상대하기 싫은 부분을 대신 상대할 수도 있고,
내가 하지 못하는 말이나 행동을 대신 해줄수도 있다.

이번에는 이러한 Proxy를 활용한 디자인 패턴인
Proxy Pattern과 DecoratorPattern에 대해 알아보자.

Proxy Pattern

사실 Proxy Pattern과 Decorator Pattern은 그 구조가 동일하다.
단지 사용하는 목적에 대해 차이를 둔다.
마치 동일한 칼을 두고 요리용과 사무용을 구분하는 것과 같다.
그렇다면 각각의 목적에 대해 살펴보자.

먼저 Proxy Pattern은 접근 제어에 목적을 둔다.
권한에 따라 실제 객체의 접근을 차단할 수도 있고,
실제 객체에 접근하지 않고 대신 역할을 수행하여 캐싱할 수도 있다.

Decorator Pattern

Decorator Pattern의 목적은 기능 추가에 있다.
프록시 패턴이던 데코레이터 패턴이던 무언가 성능을 추가할 때,
기존 클래스를 건드리는 것은 매우 위험한 작업이기 때문에
대리 객체를 두고 내부에 실제 객체를 두어 기존 클래스 수정이 아닌
단순히 덧대는 작업을 가능하게 만드는 존재가 바로 Proxy이다.

사용

그렇다면 Proxy는 어떻게 사용할 수 있는지 확인해보자.

interface Target {  // 인터페이스
	
    void action();
}

class RealTarget implements Target {  // 구현 클래스
	
    @Override
    public void action() {
    	// RealTarget 코드
    }
}

class ProxyTarget implements Target {  // ** 구현 클래스를 담은 프록시 클래스 **

	private RealTarget realTarget;  // ** 실제 구현 객체를 포함하고 있다 **
    
    public ProxyTarget(RealTarget realTarget) {
    	this.realTarget = realTarget;
    }
	
    @Override
    public void action() {
    	// ProxyTarget 추가 코드
        realTarget.action();
    }
}
    	

우선 인터페이스와 그것을 구현한 구현 클래스가 존재한다.
기본적으로 우리는 이 구조에서 다형성을 활용하여 객체를 생성한다.
하지만 여기서 실제 객체에 성능이 추가되어 변경이 필요한 경우,
추가할 성능을 실제 객체가 아닌 프록시 클래스를 만들어 별도로 추가하는 것이다.

Proxy 클래스를 생성하면 새로운 성능을 별도의 클래스에 관리하면여 SRP를 준수하면서도
기존 객체는 건드리지 않고 OCP를 준수한 채로 성능을 추가할 수 있다.

접근 제어

class ProxyTarget implements Target {  // ** 구현 클래스를 담은 프록시 클래스 **

	private RealTarget realTarget;  // ** 실제 구현 객체를 포함하고 있다 **
    
    public ProxyTarget(RealTarget realTarget) {
    	this.realTarget = realTarget;
    }
	
    @Override
    public void action() {
    	if (권한이 없는 경우) {  // ** 접근 제어 **
        	return;
        }
        realTarget.action();
    }
}

Proxy Pattern의 경우 접근 제어의 목적이 있기 때문에,
실제 객체의 메서드를 실행하기 전에 경우에 따라 실제 객체를 호출하지 않고
메서드를 종료할 수 있다.

기능 추가

class ProxyTarget implements Target {  // ** 구현 클래스를 담은 프록시 클래스 **

	private RealTarget realTarget;  // ** 실제 구현 객체를 포함하고 있다 **
    
    public ProxyTarget(RealTarget realTarget) {
    	this.realTarget = realTarget;
    }
	
    @Override
    public void action() {
    	// ** 실제 객체 호출 전 또 다른 기능 추가 **
        realTarget.action();
        // ** 실제 객체 호출 후 또 다른 기능 추가 **
    }
}

Decorator Pattern의 경우 실제 객체를 호출하는 것 이외에도
또 다른 기능을 수행하는 별도의 로직을 추가할 수 있다.

마무리

이전에 반복적인 작업을 차단하고 객체를 유연하게 변경하는
Template Method Pattern, Strategy Pattern, Template Callback Pattern에 대해 알아봤다.

Proxy의 개념이 더해진 Proxy Patter, Decorator Pattern은
이러한 디자인 패턴들과 함께 클래스의 설계를 더욱 유연하게 만들어낼 수 있다.
클래스 구조 설계 시 다양한 디자인 패턴을 기반으로 미꾸라지 같은 시스템을 만들어보자.

profile
복습에 대한 비판과 지적을 부탁드립니다

0개의 댓글