[java] 스프링 입문을 위한 자바 객체지향의 원리와 이해 정리 6

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

요리 객체 지향 프로그래밍
요리도구 4대 원칙(캡슐화, 상속, 추상화, 다형성)
요리 도구 사용법 설계 원칙(SOLID)
레시피 디자인패턴

어댑터 패턴

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

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

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

public class ClientWithNoAdapter {
  public static void main(String[] args) {
    ServiceA sa1 = new ServiceA();
    ServiceB sb1 = new ServiceB();

    sa1.runServiceA();
    sb1.runServiceB();
  }
}

49, 50 라인의 메서드는 비슷한 일을 하지만 메서드명이 다르게 호출을 해야한다.

이 같은 코드는 개인적인 경험으로는 이 두가지 함수 호출을 둘다 해야해서 둘 다 가지고 다니면서 함께 호출해야 하는 경우가 많아지고 나중에 가선 같은 일을 하는데 다른 메소드 명이 유지보수 측면에서 혼란을 줄 수 있다.


여기에

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

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

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

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

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

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

위 코드는 객체(ServiceA,B의 객체)를 속성으로 만들어서 참조하는, 즉 합성하는 디자인 패턴이다.

프록시 패턴

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

public class Service {
  public String runSomething(){
    return "서비스 짱"
  }
}

public class ClientWithNoProxy {
  public static void main(String[] args) {
    //프록시 이용하지 않은 호출

    Service service = new Service();
    System.out.println(service.runSomething());
  }
}

위 코드에 프록시 패턴을 대입하기 위해선 몇가지가 필요하다.

public interface IService {
  String runSomething();
}

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

public class Proxy implements IService {
  IService service1;

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

    // 개인적으로 생각했을 때 redux saga, epic의 역할과 비슷하다.

    service1 = new Service();

    return service1.runSomething();
  }
}

public class ClientWithProxy{
  public static void main(String[] args) {
    IService proxy = new Proxy();

    System.out.println(proxy.runSomething());
  }
}

정리

데코레이터 패턴

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

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

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

public class Decorator implements IService {
  IService service1;

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

    // 개인적으로 생각했을 때 redux saga, epic의 역할과 비슷하다.

    service1 = new Service();

    return "wow" + service1.runSomething();
  }
}

public class ClientWithDecorator{
  public static void main(String[] args) {
    IService decorator = new Decorator();

    System.out.println(decorator.runSomething());
  }
}

정리

싱글턴 패턴

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

커넥션 풀, 스레드 풀, 디바이스 설정 객체 등 한가지 상태로 관리되어야 하는 클래스등에 적합한 패턴.

필요 조건

JS에서는 class를 파일에 작성하고 export new 클래스 방식으로 이를 싱글톤으로 사용해본 경험이 있다.
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) {

    //프라이빗 생성자를 사용했기 때문에 new 를 통한 생성자 생성이 불가하다.
    //Singleton S = new Singleton();

    Singleton s1 = Singleton.getInstance();
    Singleton s2 = Singleton.getInstance();

  }
}

위의 2개의 객체 참조 변수는 하나의 단일 객체를 참조한다.

싱글톤 패턴이 안티패턴으로 적용되는 부분

private 생성자를 갖고 있기 때문에 상속을 할 수 없다.

- 자기자신만이 본인 오브젝트를 만들 수 있도록 제한하기 때문에 private 생성자를 사용했다. 하지만 이렇기 때문에 상속이 불가능하며, 이는 다형성또한 제공할 수 없다. 객체지향 설계를 적용할 수 없다는 것.

테스트하기 힘들다.

- 만들어지는 방식이 제한적이기 때문에 mock오브젝트 등으로 대체하기 힘들다.

서버환경에서는 1개의 instance를 보장하지 못한다.
- 서버에서 클래스로더를 어떻게 구성하고 있느냐에 따라서 하나이상의 instnace가 만들어질 수 있다. 따라서 java환경에서 싱글톤이 보장된다할 수 없을 수 있다.

전역 상태를 만들 수 있기 때문에 바람직하지 못하다.
- Sington은 어디에서든지 누구나 접근할 수 있다. 그리고 어디든지 사용될 수 있개 때문에 아무 객체가 자유롭게 접근하여 수정하고 데이터를 공유할 수 있는 상태가 될 수 있다는 것이다. 이러한 것은 객체지향에서는 권장되지 않는 프로그래밍 모델이다.

템플릿 메서드 패턴

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

public class Dog {
  public void playWithOwner() {
    System.out.println("1");
    System.out.println("Dog");
    System.out.println("2");
    System.out.println("3");
  }
}

public class Cat {
  public void playWithOwner() {
    System.out.println("1");
    System.out.println("Cat");
    System.out.println("2");
    System.out.println("3");
  }
}

위처럼 행동이 비슷하지만 한가지 메서드가 다를 때 상속을 활용하여 동일한 부분은 상위 클래스로 달라지는 부분만 하위 클래스로 나누는게 좋다고 생각할 것이다.

public abstract class Animal {

  public void playWithOwner() {
    System.out.println("1");
    play();
    runSomething();
    System.out.println("3");
    )
  }

  abstract void play();

  void runSomething() {
    System.out.println("2");
  }
}

public class Dog extends Animal {

  @Override
  //추상 메서드 오버라이딩
  void play() {
    System.out.println("Dog");
  }

  @Override
  //Hook(갈고리)메서드 오버라이딩
  void runSomething(){
    "123Dog"
  }
}

public class Cat extens Animal {
  @Override
  //추상 메서드 오버라이딩
  void play() {
    System.out.println("Cat");
  }

  @Override
  //Hook(갈고리)메서드 오버라이딩
  void runSomething(){
    "123Cat"
  }

}

위 코드처럼 개선이 가능하다.

하위 클래스인 Dog와 Cat은 상위 클래스 Animal에서 구현을 강제하는 play를 반드시 구현해야 한다. 이처럼 상위 클래스에 공통 로직을 수행하는 템플릿 메서드와 하위 클래스에 오버라이딩을 강제하는 추상메서드 또는 선택적으로 오버라이딩 할 수 있는 훅 메서드를 두는 패턴을 템플릿 메서드 패턴이라고 한다.

팩터리 메서드 패턴

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

클래스간의 결합도를 낮추기 위해 사용된다. 여기서 결합도란 클래스의 변경점이 생겼을 때 얼마나 다른 클래스에도 영향을 주는가를 뜻한다. 팩토리 메서드를 적용함으로써 직접 객체를 생성해 사용하는 것을 방지하고 서브 클래스에 위임함으로써 보다 효율적인 코드제어가 가능하고 의존성을 제거한다.


public abstract class Animal {
  abstract AnimalToy getToy();
}

public abstract class AnimalToy {
  abstract void identify();
}


public class Dog extends Animal {
  @Override
  AnimalToy getToy() {
    return new DogToy();
  }
}

public class DogToy extends AnimalToy {
  public void identify() {
    System.out.println("나는 테니스공, 강아지 장난감이다")
  }
}

public class Driver {
  public static void main(String[] args) {
    Animal bolt = new Dog();

    AnimalToy boldBall = bold.getToy();

    boltBall.identify();

  }
}

전략 패턴

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

필요조건

클라이언트는 다양한 전략 중 하나를 선택해 컨텍스트에 주입한다.

군인이 있고, 군인이 사용할 수 있는 무기가 있다. 보급 장교가 군인에게 무기를 주면 군인은 이를 사용한다.

이때 무기는 전략, 군인은 컨텍스트, 보급 장교는 클라이언트로 구분할 수 있따.

public interface Strategy {
  public abstract void runStrategy();
}

public class StrategyGun implements Strategy {
  @Override
  public void runStrategy() {
    System.out.println("탕탕")
  }
}

public class StrategySword implements Strategy {
  @Override
  public void runStrategy() {
    System.out.println("채챙")
  }
}

public class StrategyBow implements Strategy {
  @Override
  public void runStrategy() {
    System.out.println(슝슝슝")
  }
}

public class Soldier {
  void runContext(Strategy strategy) {
    System.out.println("전쟁이다");
    strategy.runStrategy();
    System.out.println("전쟁종료")
  }
}

public class Client {
  public static void main(String[] args) {
    Strategy strategy = null;
    Soldier rambo new Soldier();

    strategy = new StrategyGun();
    rambo.runContext(strategy);

  }
}

위 코드처럼 전략을 다양하게 변경하면서 컨텍스트의 실행이 가능하다. 전략 패턴은 이미 다양한 곳에서 문제 해결을 위해 사용되고 있다.

템플릿 메서드와 비슷하게 이용된다. 다만 단일 상속만이 가능한 자바에서는 전략 패턴을 더 많이 사용한다.

템플릿 콜백 패턴

전략 패턴에서 이를 익명 내부 클래스로 변경하여 구현한 패턴

위의 전략 패턴에서 전략 객체를 생성하는 부분을 전부 익명 내부 클래스로 치환하여 적용하는 패턴이다. 하지만 그렇게 하면 코드수가 늘어나고 추후에 DIP를 위반하기 쉽다.


public interface Strategy {
  public abstract void runStrategy();
}

public class Soldier {
  void runContext (String weaponSound) {
    System.out.println("전투시작);
    executeWeapon(weaponSound).runStrategy();
    System.out.println("전투종료");
  }

  private Strategy executeWeapon(final String weaponSound) {
    return new Strategy() {
      @Override
      public void runStrategy() {
        System.out.println(weaponSound);
      }
    }
  }
}

public class Client {
  public static void main(String[] args) {
    Soldier rambo = new Soldier();

    rambo.runContext("총!");

  }
}