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

prana·2023년 11월 29일

JAVA

목록 보기
6/8
post-thumbnail

질문

  1. 200p - 디자인 패턴은 객체 지향의 특성 중 00(extends), 00000(interface/implements), 00(객체를 속성으로 사용)을 이용한다.

    상속, 인터페이스, 합성

  2. 201p - 어댑터 패턴은 00 00 00을 활용한 설계 패턴이라고 할 수 있다.

    개방 폐쇄의 원칙


내용

디자인 패턴

실제 개발 현장에서 비즈니스 요구 사항을 프로그래밍으로 처리하면서 만들어진 다양한 해결책 중에서, 많은 사람들이 인정한 베스트 프랙티스를 정리한 것이다.

  • 객체지향 특성과 설계 원칙을 기반으로 구현돼 있다.

스프링 프레임워크

  • 자바 엔터프라이즈 개발을 편하게 해주는 오픈소스 경량급 애플리케이션 프레임워크
  • OOP 프레임워크

어댑터 패턴(Adapter Pattern)

변환기(converter), 서로 다른 두 인터페이스 사이에 통신이 가능하게 하는 것.

호출 당하는 쪽의 메서드를 호출하는 쪽의 코드에 대응하도록 중간에 변환기를 통해 호출하는 패턴

  • 우측 클라이언트(ClientWithAdapter)가 변환기를 통해 runService()라는 동일한 메서드명으로 두 객체의 메서드를 호출하는 것을 볼 수 있다.

프록시 패턴(Proxy Pattern)

  • 대리자, 대변인이라는 뜻을 가진 단어다.
  • 다른 누군가를 대신해 그 역할을 수행하는 존재를 말한다.

1) 대리자를 사용하지 않고 직접 호출하는 구조

2) 프록시 패턴이 적용된 경우

  • 실제 서비스 객체가 가진 메서드와 같은 이름의 메서드를 사용하는데, 이를 위해 인터페이스를 사용한다.

  • 인터페이스를 사용하면 서비스 객체가 들어갈 자리에 대리자 객체를 대신 투입해 클라이언트 쪽에서는 실제 서비스 객체를 통해 메서드를 호출하고 반환값을 받는지, 대리자 객체를 통해 메서드를 호출하고 반환값을 받는지 전혀 모르게 처리할 수도 있다.

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

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

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

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

프록시 패턴

  • 제어 흐름을 조정하기 위한 목적으로 중간에 대리자를 두는 패턴
  • 개방 폐쇄의 원칙(OCP)에서 예로 들었던 마티즈와 쏘나타, 그리고 자동차가 생각날 것이다.

또한 인터페이스를 중간에 두고 스노우타이어와 일반 타이어, 광폭 타이어를 서로 교체해 줘도 영향받지 않았던 자동차를 예로 들었던 의존 역전의 원칙(DIP)도 적용된 설계 패턴이라 볼 수 있겠다.

데코레이터 패턴(Decorator Pattern)

  • 도장/도배업자를 의미한다.

  • 장식자: 원본에 장식을 더하는 패턴

  • 프록시 패턴과 구현 방법이 같다.

  • 다만 프록시 패턴은 클라이언트가 최종적으로 돌려받는 반환값을 조작하지 않고 그대로 전달하는 반면, 데코레이터 패턴은 클라이언트가 받는 반환값에 장식을 덧입힌다.

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

  • Proxy(아래)와 Decorator(위)의 차이는 다음과 같은 것 같다. 장식을 더한다는 느낌?
  • 장식자는 실제 서비스와 같은 이름의 메서드를 구현한다. 이때 인터페이스를 사용한다.
  • 장식자는 실제 서비스에 대한 참조변수를 갖는다. (합성)
  • 장식자는 실제 서비스의 같은 이름을 가진 메서드를 호출, 그 반환값에 장식을 더해 클라이언트에게 돌려준다.
  • 장식자는 실제 서비스의 메서드 호출 전후에 별도의 로직을 수행할 수도 잇다.

데코레이터 패턴

  • 메서드 호출의 반환값에 변화를 주기 위해 중간에 장식자를 두는 패턴

싱글톤 패턴(Singleton Pattern)

  • 클래스의 인스턴스, 즉 객체를 하나만 만들어 사용하는 패턴

인스턴스를 하나만 만들어 사용하기 위한 패턴이다.

  • 오직 인스턴스를 하나만 만들고, 그것을 계속해서 재사용한다.
  • 의미상 두 개의 객체가 존재할 수 없다.
  • new를 실행할 수 없도록 생성자에 private 접근 제어자를 지정한다.
  • 유일한 단일 객체를 반환할 수 있는 정적 메서드가 필요하다.
  • 유일한 단일 객체를 참조할 수 있는 정적 참조 변수가 필요하다.

  • 단일 객체인 경우 결국 공유 객체로 사용되기 때문에 속성을 갖지 않게 하는 것이 정석이다. 단일 객체가 속성을 갖게 되면 하나의 참조변수가 변경한 단일 객체의 속성이 다른 참조 변수에 영향을 미치기 때문이다.

  • 👉 전역/공유 변수를 가능한 한 사용하지 말라는 지침과 일맥상통한다.

    • 다만 읽기전용 속성을 갖는 것은 문제되지 않는다.
    • 단일 객체가 다른 단일 객체에 대한 참조를 속성으로 가진 것 또한 문제가 되지 않는다. (스프링의 싱글톤 빈이 가져야 할 제약 조건)

  • 참조하고 있는 객체의 toString() 메서드가 호출된다. 별도 오버라이딩 하지 않았다면, 객체의 고유 값인 hashcode를 반환하게 된다.

  • s1, s2, s3가 동일한 객체를 참조한다는 것을 의미한다.

싱글톤 패턴의 특징

  • private 생성자를 갖는다.
  • 단일 객체 참조 변수를 정적 속성으로 갖는다.
  • 단일 객체 참조 변수가 참조하는 단일 객체를 반환하는 getInstance() 정적 메서드를 갖는다.
  • 단일 객체는 쓰기 가능한 속성을 갖지 않는 것이 정석이다.

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

  • 상위 클래스의 견본 메서드에서 하위 클래스가 오버라이딩한 메서드를 호출하는 패턴이다.

  • 객체 지향의 4대 특성 가운데 상속을 통해 동일한 부분(중복)은 상위 클래스로, 달라지는 부분만 하위 클래스로 분할하고 싶은 객체 지향 설계에 대한 욕구가 자극될 것이다.


  • Animal 상위 클래스는 템플릿을 제공하는 playWithOwner() 메서드와 하위 클래스에게 구현을 강제하는 play() 추상 메서드, 하위 클래스가 선택적으로 오버라이디이 할 수 있는 runSomething() 메서드가 있다.

상위 클래스에 공통 로직을 수행하는 템플릿 메서드와,
하위 클래스에 오버라이딩을 강제하는 추상 메서드 또는 선택적으로 오버라이딩 할 수 있는 훅(Hook) 메서드를 두는 패턴을 템플릿 메서드 패턴이라고 한다.

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

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

  • 공장-> 물건을 생산, 객체 지향에서는 팩터리는 객체를 생성한다.

    객체를 생성 반환하는 메서드를 말한다.

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

오버라이드된 메서드가 객체를 반환하는 패턴

  • 의존 역전의 원칙을 활용하고 있음을 알 수 있다.

전략 패턴(Strategy Pattern)

  • 전략 메서드를 가진 전략 객체
  • 전략 객체를 사용하는 컨텍스트(전략 객체의 사용자/소비자)
  • 전략 객체를 생성해 컨텍스트에 주입하는 클라이언트(제 3자, 전략 객체의 공급자)

  • 군인이 있다고 상상, 그 군인이 사용할 무기가 있다고 가정
  • 보급 장교가 무기를 군인에게 지급해 주면 군인은 주어진 무기에 따라 전투를 수행하게 된다.
  • 무기: 전략, 군인: 컨텍스트, 보급 장교: (제3자인)클라이언트가 된다.



-템플릿 메서드 패턴과 유사하다.

  • 같은 문제의 해결책으로 상속을 사용하는 템플릿 메서드 패턴과 객체 주입을 통한 전략 패턴 중에서 선택/적용할 수 있다.

단일 상속만이 가능한 자바 언어에서는 상속이라는 제한이 있는 템플릿 메서드 패턴보다는 전략 패턴이 더 많이 활용된다.

템플릿 콜백 패턴(Template Callback Pattern - 견본/회신 패턴)

  • 전략 패턴의 변형으로, 스프링의 3대 프로그래밍 모델 중 하나인 DI(의존성 주입)에서 사용하는 특별한 형태의 전략 패턴이다.
  • 전략을 익명 내부 클래스로 정의해서 사용한다는 특징이 있다.
  • 앞에서 본 전략 패턴 코드를 템플릿 콜백 패턴으로 바꿔보자.
  • 익명 내부 클래스를 사용하기 때문에 무기 구현 클래스는 필요가 없다.



  • 활 대신 도끼를 쓴다는 것 외에 실행 결과는 기존 전략 패턴과 차이가 없다.
    코드를 보면 많은 부분에서 중복되는 것을 볼 수 있다.

  • Strategy.java의 코드는 동일하다.


  • 클라이언트 코드가 상당히 깔끔해졌다.
  • 중복되는 부분을 컨텍스트로 이관했기 때문이다.
  • 리팩터링된 템플릿 콜백 패턴을 DI에 적극 활용하고 있다. 따라서 스프링을 이해하고 활용하기 위해서는 전략 패턴과 템플릿 콜백 패턴, 리팩터링된 템플릿 콜백 패턴을 잘 기억해두자.

전략을 익명 내부 클래스로 구현한 전략 패턴

  • 개방 폐쇄의 원칙(OCP)와 의존 역전 원칙(DIP)이 적용된 설계 패턴이다.

내부 클래스와 내부 익명 클래스

  • 내부 클래스: 다른 클래스 내부에 선언되고, 사용되는 클래스
  1. 내부 클래스(Inner Class): 다른 클래스 내부에 선언되고, 외부 클래스의 멤버 변수 및 메서드에 접근할 수 있습니다.
  • 멤버 내부 클래스: 외부 클래스의 멤버 변수처럼 동작, 외부 클래스의 모든 멤버에 접근 가능하다.
  • 정적 내부 클래스(Static inner class): 외부 클래스의 인스턴스와 상관없이 정적으로 선언되며, 주로 유틸리티 클래스 등을 정의할 때 사용된다.
button.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        // 이벤트 발생 시 실행되는 코드
    }
});
  • ActionListener 인터페이스를 구현하는 익명 내부 클래스를 생성, addActionListener 메서드에 전달한다.
  • 이 경우에는 클래스 이름을 명시적으로 작성하지 않고도 인터페이스를 구현할 수 있다.
// 명시적인 내부 클래스 
class Outer {
    private int outerField = 10;
    
    // 멤버 내부 클래스
    class Inner {
        public void display() {
            System.out.println("OuterField: " + outerField);
        }
    }
    
    public void callInner() {
        Inner innerObject = new Inner();
        innerObject.display();
    }
}
  • 명시적 내부 클래스: 이름이 있는 클래스로, 외부 클래스의 인스턴스를 만들어야 하며, 다른 곳에서도 사용할 수 있다. 그러나 더 많은 코드와 별도의 클래스 파일이 필요할 수 있다.
// 익명 내부 클래스 
interface Greeting {
    void greet();
}

class Outer {
    public void displayGreeting() {
        Greeting greeting = new Greeting() {
            public void greet() {
                System.out.println("Hello, this is an anonymous inner class!");
            }
        };
        greeting.greet();
    }
}
  • 한 번만 사용되는 간단한 구현을 제공하며, 코드를 줄이고 특정한 상황에 사용하기 적합하다. 재사용이 어렵고, 가독성을 해칠 수 있다.

인터페이스는 new 객체로 생성되어질 수 없지 않나?

😉ChatGpt왈

  • 일반적으로 자바에서 인터페이스는 직접적으로 객체로 생성될 수 없습니다. 인터페이스는 구현이 제공되지 않은 추상적인 메서드들의 집합이기 때문에, 인터페이스를 직접적으로 인스턴스화하는 것은 불가능합니다.
  • 그러나 익명 내부 클래스를 사용하여 인터페이스를 구현하는 객체를 생성할 수 있습니다.
  • 익명 내부 클래스는 인터페이스나 추상 클래스를 사용하여 즉석에서 구현하고 인스턴스화할 수 있는 방법을 제공합니다.
  • 예를 들어, 인터페이스 Greeting을 구현하는 객체를 익명 내부 클래스로 생성할 수 있습니다:
interface Greeting {
    void greet();
}

class Example {
    public void displayGreeting() {
        Greeting greeting = new Greeting() { // 익명 내부 클래스
            public void greet() {
                System.out.println("Hello, this is an anonymous inner class!");
            }
        };
        greeting.greet();
    }
}
  • 위의 예시에서 Greeting 인터페이스를 구현하는 익명 내부 클래스를 생성하고 있습니다. 이 내부 클래스는 greet 메서드를 구현하며, displayGreeting 메서드 내에서 해당 인터페이스를 이용해 객체를 생성하고 greet 메서드를 호출합니다.

요약하자면, 인터페이스는 직접 객체로 생성될 수는 없지만, 익명 내부 클래스를 활용하여 해당 인터페이스를 구현한 객체를 생성할 수 있습니다.

0개의 댓글