스프링이 사랑한 디자인 패턴

디자인 패턴은 객체 지향의 특성 중 상속(extends), 인터페이스(implements), 합성(객체를 속성으로 사용) 을 이용한다. 다른 방식은 없다.

1. 어댑터 패턴(Adapter Pattern)

https://refactoring.guru/ko/design-patterns/adapter

  • 두 인터페이스가 공통된 규격이 없어 사용이 불가할 때(혹은 공통된 규격을 사용하고 싶을 때), 어댑터는 변환기로 공통된 규격을 통해 통신할 수 있도록 한다.

    • 인터페이스 상속(implements) 을 통해서 구현

    • 예시

      • 다양한 데이터베이스 시스템을 공통의 인터페이스인 JDBC 를 통해 조작한다.

      • 자바에서는다양한 운영체제의 기계어를 JVM 을 통해서 만들어낸다.

  • 한문장으로 정리하면 다음과 같다. "인터페이스가 맞지 않는 두 객체 간의 협력을 가능하게 만드는패턴"

어댑터 패턴이 적용되지 않은 코드

  • main() 메서드를 살펴보면 sa, sb 를 통해 호출되는 메서드가 비슷한 일을 하지만, 다른 메서드 명을 사용하고 있는 것을 볼 수 있다.

public class ServiceA {
    void runServiceA() {
        System.out.println("ServiceA");
    }
}

public class ServiceB {
    void runServiceB() {
        System.out.println("ServiceB");
    }
}

public class NoAdapter {
    public static void main(String[] args) {
        ServiceA sa = new ServiceA();
        ServiceB sb = new ServiceB(); 
        
        sa.runServiceA();
        sb.runServiceB(); 
    }
}

어댑터 패턴이 적용된 코드

어댑터 패턴을 적용해 메서드 명을 통일해보자.

인터페이스를 토입해 조금 더 개선해보자.

2. 프록시 패턴(Proxy Pattern)

https://refactoring.guru/ko/design-patterns/proxy

  • 어뎁터 패턴과 비슷해 보이지만, 아래와 같은 시점으로 다른 디자인 패턴이기에 차이가 있다.

    • 어댑터 패턴 : 호환성 제공이 목표

    • 프록시 패턴 : 원래 객체에 대한 접근 제어가 주요 목표 (프록시 객체는 원래 객체에 대한 대리자 역할)

  • 프록시 패턴 구현의 중요 포인트

    • 대리자는 실제 서비스와 같은 이름으로 메서드를 구현한다. 이 때 인터페이스를 사용한다.

    • 대리자는 실제 서비스에 대한 참조 변수를 갖는다(합성).

    • 대리자는 실제 서비스의 같은 이름을 가진 메서드를 호출하고 그 값을 클라이언트에게 돌려준다.

    • 대리자는 실제 서비스의 메서드 호출 전후의 별도의 로직을 수행할 수 있다.

  • 프록시 패턴은 실제 서비스 메서드의 반환 값에 가감하는 것을 목적으로 하지 않고 제어의 흐름을 변경하거나 다른 로직을 수행하기 위해 사용한다.

    • 실제 서비스 메서드의 반환 값에 가감하는 것을 목적으로 하는 것은 데코레이터 패턴이다.

  • OCP(Open Closed Principle) DIP(Dependency Inversion Principle) 원칙을 살펴볼 수 있다.

    • OCP : 배우가 바뀌어도 배역의 역할은 변경되지 않는다.

    • DIP : 자동차가 타이어에 의존해야지 스노우 타이어에 의존해서는 안된다.

프록시 패턴이 적용되지 않은 코드

프록시 패턴이 적용된 코드

  • 프록시 패턴의 경우 실제 서비스가 가진 메서드와 같은 이름의 메서드를 사용하는데, 이를 위해서 인터페이스를 사요한다.

  • 인터페이스를 사용하면 서비스 객체가 들어갈 자리에 프록시 객체를 대신 투입해 클라이언트 쪽에서는

    • 실제 서비스 객체를 통해서 메서드를 호출하고 반환값을 받는지,

    • 아니면 대리자 객체를 통해서 메서드를 호출하고 반환값을 받는지 전혀 모르게 처리할 수 있다.

3. 데코레이터 패턴(Decorator Pattern)

https://refactoring.guru/ko/design-patterns/decorator

상속의 단점

  • 상속은 정적이다 : 런타임 때 기존 객체의 행동을 변경할 수 없다.

  • 자식 클래스는 하나의 부모 클래스만 가질 수 있다. 대부분 언어에서는 상속은 클래스가 동시에 여러 클래스가 동시에 여러 클래스의 행동을 상속하도록 허용하지 않는다.

  • 프록시 패턴과 구현 방법은 동일하지만, 목적이 다르다.

    • 프록시 패턴 : 제어의 흐름을 변경하거나 별도의 로직 처리를 목적으로 한다. 클라이언트가 받는 반환값을 특별한경우가 아니면 변경하지 않는다.

    • 데코레이터 패턴 : 클라이언트가 받는 반환값에 장식을 더한다.

4. 싱글턴 패턴(Singleton Pattern)

  • 싱글턴 패턴은 인스턴스를 하나만 만들어서 사용하기 위한 패턴이다.

    • 커넥션 풀, 스레드 풀, 디바이스 설정 객체 등과 같은 경우 여러 인스턴스를 만들게 되면 불필요한 자원을 사용하게 되고, 또 예상치 못한 결과를 맞이할 수 있다.

    • 싱글턴 패턴은 오직 인스턴스를 하나만 만들고 그것을 계속해서 사용하는 것이다.

  • 싱글턴 패턴을 적용할 경우 의미상 두 개의 객체가 존재할 수 없다.

  • 이를 위한 요소를 생각하면 다음과 같다.

    • new 를 실행할 수 없도록 생성자에 private 접근제어자 사용

    • 유일한 단일 객체를 반환할 수 있는 static 메서드 필요

    • 유일한 단일 객체를 참조할 static 참조 변수 필요

  • static 을 사용하므로 프로세스 내에서 공유가 가능한 참조 변수를 사용할 수 있게 된다.

주의점!

  • 싱글톤 패턴은 전역 변수와 비슷한 특성을 갖게 되어서 공유 자원을 갖게 된다면 코드의 복잡성을 높일 수 있다.

  • 때문에, 싱글톤 객체 내에는 속성을 갖지 않도록! 하는 것이 정석이다.

  • 다만 읽기 전용 속성을 갖는 것은 문제가 되지 않는다.

싱글톤 패턴이 안티 패턴으로 불리는 이유

  1. 싱글톤 패턴은 간단하고 명료한 해결책을 제시하지만, 장기적인 유지보수성과 테스트 가능성에 악영향을 미친다.

  2. 작은 프로젝트에서는 유용할 수 있으나, 대규모 시스템에서는 전역 상태관리와 결합도 증가 문제로 인해 권장되지 않는다.

  3. 싱글톤 대신 DI 컨테이너나 팩토리 패턴 등 더 나은 설계 방식을 고려하는 것이 좋다.

5. 템플릿 메서드 패턴(Template Method Pattern)

  • 템플릿 메서드 패턴은 상위 클래스에 골격(틀) 을 정의하고, 구체적인 구현은 하위 클래스에서 제공하는 디자인 패턴.

    • 추상클래스, 인터페이스를 통해서 구현

    • "상위 클래스의 템플릿 메서드에서 하위 클래스가 오버라이딩한 메서드를 호출하는 패턴"

  • 템플릿 메서드 패턴은 의존 역전 원칙(DIP) 를 활용하고 있음을 알 수 있다.

템플릿 메서드 패턴이 적용되지 않은 코드

  • 강아지, 고양이 클래스 모두 playWithOwner() 메서드 내에서 2번째 문자열 출력을 제외하고는 다른 내용이 없다. (반복된 코드..)

템플릿 메서드 패턴이 적용된 코드

  • Animal 이라는 추상클래스에서 틀을 만들고, 이를 상속한 하위 클래스에서 구현을 제공한다.

  • main 메서드는 구체화된 클래스(Dog, Cat)가 아닌, 추상화된 클래스(Animal)를 의존하기 때문에 DIP 원칙이 지켜진 것을 확인할 수 있다.

6. 팩터리 메서드 패턴(Factory Method Pattern)

  • 팩터리는 공장을 의미한다. 공장은 물건을 생산하는데, 객체지향의 세계에서는 공장은 객체를 생산한다.

  • 결국, 팩터리 메서드는 객체를 생성 반환하는 메서드를 의미한다.

  • 여기에, 패턴이 붙으면 하위 클래스에서 팩터리 메서드를 오버라이딩해서 객체를 반환하게 하는 것을 의미한다.

  • 팩토리 메서드 패턴 또한 의존 역전 원칙(DIP) 를 활용하고 있음을 알 수 있다.

7. 전략 패턴(Strategy Pattern)

  • 전략 패턴을 구성하는 요소 세가지

    • 전략 메서드를 가진 전략 객체 -> 각각의 무기. 총, 검, 활 등등 ..

    • 전략 객체를 사용하는 컨텍스트(전략 객체의 사용자/소비자) -> 무기를 사용할 군인

    • 전략 객체를 생성해 컨텍스트에 주입하는 클라이언트(제3자, 전략 객체의 공급자) -> 군인에게 무기를 전달해 줄 보급 장교

  • 같은 문제를 템플릿 메서드 방법을 통해서도 해결할 수 있다.

    • 템플릿 메서드 : 상속을 이용

    • 전략 패턴 : 객체 주입을 이용

  • "클라이언트가 전략을 생성해서 전략을 실행한 컨텍스트에 주입하는 패턴"

  • 전략 메서드 패턴은 개방 폐쇄 원칙(OCP), 의존 역전 원칙(DIP) 를 활용하고 있음을 알 수 있다.

8. 템플릿 콜백 패턴

  • 템플릿 콜백 패턴은 전략 패턴의 변형으로, 스프링의 3대 프로그래밍 모델 중 하나인 DI(의존성 주입) 에서 사용되는 특별한 형태의 전략 패턴이다.

  • 템플릿 콜백 패턴은 전략 패턴과 모든 것이 동일한데, 전략을 익명 내부 클래스로 정의해서 사용한다는 특징이 있다.

  • 템플릿 콜백 패턴 또한 개방 폐쇄 원칙(OCP), 의존 역전 원칙(DIP) 를 활용하고 있음을 알 수 있다.

Last updated