[자바 객체 지향] 스프링이 사랑한 디자인 패턴 1

2021. 8. 16. 13:15CSE/JAVA

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

 

1. 어댑터 패턴(Adpater Pattern)

- 어댑터 (Adapter) : 변환기. 즉, 서로 다른 인터페이스 사이에 통신이 가능하게 하는 것이다.

- 어댑터 패턴은 개방 폐쇄 원칙을 활용한 설계 패턴이라고 할 수 있다.

 

(1) ServiceA.java

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

(2) ServiceB.java

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

(3) AdapterServiceA.java

public class AdapterServiceA {
    ServiceA sa1 = new ServiceA();

    void runService(){
        sa1.runServiceA();
    }
}

(4) AdapterServiceB.java

public class AdapterServiceB {
    ServiceB sb1 = new ServiceB();

    void runService(){
        sb1.runServiceB();
    }
}

(5) ClientWithAdapter.java

public class ClientWithAdapter {
    public static void main(String[] args) {
        AdapterServiceA asa1 = new AdapterServiceA();
        AdapterServiceB asb1 = new AdapterServiceB();

        asa1.runService();
        asb1.runService();
    }
}

 

 

- (3),(4)는 (1),(2)의 메서드를 runService()라고 하는 같은 이름의 메서드로 호출해서 사용할 수 있게 해주는 변환기다.

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

 

2. 프록시 패턴(Proxy Pattern)

- 프록시 : 대리자, 대변인

 

(1) IService.java (인터페이스)

public interface IService {
    String runSomething();
}

(2) Service.java

public class Service implements IService{
    public String runSomething(){
        return "서비스 짱!!!";
    }
}

(3) Proxy.java

public class Proxy implements IService{
    IService service1;

    public String runSomething(){
        System.out.println("호출에 대한 흐름 제어가 주목적, 반환 결과를 그대로 전달");

        service1 = new Service();
        return service1.runSomething();
    }
}

(4) ClientWithProxy.java

public class ClientWithProxy {
    public static void main(String[] args) {
        IService proxy = new Proxy();
        System.out.println(proxy.runSomething());
    }
}

(5) 프록시 패턴을 적용한 후 시퀀스 다이어그램

(6) 프록시 패턴의 중요 포인트

  • 대리자는 실제 서비스와 같은 이름의 메서드를 구현한다. 이때 인터페이스를 사용한다
  • 대리자는 실제 서비스에 대한 참조 변수를 갖는다(합성)
  • 대리자는 실제 서비스의 같은 이름을 가진 메서드를 호출하고 그 값을 클라이언트에게 돌려준다
  • 대리자는 실제 서비스의 메서드 호출 전후에 별도의 로직을 수행할 수도 있다

 

- 프록시 패턴은 제어 흐름을 조정하기 위한 목적으로 중간에 대리자를 두는 패턴

- 위의 예제에서 살펴본 프록시 패턴은 개방 폐쇄 원칙과 의존 역전 원칙이 적용된 설계 패턴이다.

 

 

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

- 데코레이터 : 장식자

- 데코레이터 패턴은 프록시 패턴과 구현 방법이 같지만, 클라이언트가 받는 반환값에 장식을 덧입힌다는 점이 다르다.

- 데코레이터 패턴 : 메서드 호출의 반환값에 변화를 주기 위해 중간에 장식자를 두는 패턴

 

(1) 데코레이터 패턴의 중요 포인트

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

 

4. 싱글턴 패턴 (Singleton Pattern)

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

- 따라서 이를 구현하라면 다음 세 가지가 반드시 필요하다.

  • new를 실행할 수 없도록 생성자에 private 접근 제어자를 지정한다
  • 유일한 단일 객체를 반환할 수 있는 정적 메서드가 필요하다
  • 유일한 단일 객체를 참조할 정적 참조 변수가 필요하다
public class Singleton {
    static Singleton singletonObject; //정적 참조 변수

    private Singleton(){}; //private 생성자

    //객체 반환 정적 메서드
    public static Singleton getInstance(){
        if (singletonObject == null){
            singletonObject = new Singleton();
        }

        return singletonObject;
    }
}
public class Client {
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        Singleton s3 = Singleton.getInstance();

        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);

        s1 = null;
        s2 = null;
        s3 = null;
    }
}

- 단일 객체인 경우, 결국 공유 객체로 사용되기 때문에 속성을 갖지 않게 하는 것이 정석

=> 단일 객체가 속성을 갖게 되면 하나의 참조 변수가 변경한 단일 객체의 속성이 다른 참조 변수에 영향을 미치기 때문이다

=> 다만 읽기 전용 속성을 갖는 것과 다른 단일 객체에 대한 참조를 속성으로 가진 것은 괜찮음

 

- 기억해 둘 싱글턴 패턴의 특징

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

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