[TIL - 1002] 템플릿 메소드/데코레이터 패턴, I/O 모델

디자인패턴

템플릿 메소드 패턴

  • 전체적인 흐름(로직)은 같으나 일부분 구현 클래스마다 유연성을 부여해주는 문제 해결 방법 : 중복되는 코드를 줄이고 흐름은 일괄적으로 하되 유연성을 부여할 수 있는 방법, 해당 유연성 부여 메소드는 외부 인터페이스로 공개하지않음
public abstract class Car {
    private boolean isStart;

    public Car {
        isStart = false; // 명시적
    }

    public void drive() {
        if (!isStart) {
            start();
            isStart = true;
        }

        System.out.println("붕붕");
    }

    abstract void start();
}

public class ManualCar extends Car {
    
    @Override
    void start() {

    }
}

public class AutoCar extends Car {

    @Override
    void start() {
      
    }
}

템플릿 메소드 패턴 - 경험해본 부분

  • 볼링점수판 구현할 때 사용해봄 : 노말프레임(1~9), 라스트프레임(10)은 서로 같은 필드(점수)를 가지고 다르게 다르게 동작함 - 처음 분석했을 때 필드는 같고 다르게 동작한다는 이유만으로 템플릿 메소드 패턴을 사용했었음
  • 원인 : 잘못된 객체 설계(필드는 공통적인데 행동은 각각 구현해야할 때 잘못된 패턴 적용으로 인해 오히려 더 복잡성을 높이게 됨 - 해당 패턴 적용이 아니라 객체 추상화를 다시 해야했음), 때에 맞지 않는 디자인패턴 적용(같은 필드를 하위에서 상위 메소드 호출 없이 사용할 수 있게끔 하다보니 잘못된 곳에서 해당 패턴을 적용해버렸던 것)
/* 잘못 생각해서 짰었던 코드 */
public abstract Frame {
    
    private ArrayList<Integer> pins = new ArrayList<>();

    public int bowl(int pinNums) {
        record(pins); // 지금 생각해보면 로직 자체가 동일하다고 볼 수도 없음 - 상태를 전달하기위한 수단에 불과했던 코드
    }

    abstract void record(List<Intenger> pins);
}
  • 해결 : 객체 나눔 - 템플릿 메소드 패턴 제거, abstract 클래스가 아닌 인터페이스로 변경(점수라는 필드를 누가 가지고 있을지 다시 생각), 추상화 작업 다시(프레임에는 최초 기본 상태가 주어지고 점수에 따라 상태가 변경되는 것으로 - 어떤 객체가 있는지 다시 파악하고, 주요 역할이 어떤 객체인지 등을 다시 파악)

템플릿 메소드 패턴 - 생각되는 부분

  • 큰 흐름은 같되 구현 클래스마다 세부 동작이 다를 때 적용하는데, 만약 구현 클래스 중 따르지 않고 해당 큰 메소드를 오버라이드해서 사용한다고 할 때 굳이 필요없는 메소드를 반드시 구현해야하는 상황이 오지 않을까? 이미 그렇게 구현해야하는 상황이 온다면 구조적으로 다시 모델링을 해야하나? : 고정적인 부분은 변경을 막아버리기 위해서 final로 처리하기, 변경해야한다면 그때는 구조적으로 전체 다시 짜야한다고 생각하는 것이

  • 어떤 문제 상황을 해결하기위해 나온지 모르고 겉만 보고 패턴을 적용하면 위와 같은 잘못된 코드가 나옴 : 디자인 패턴을 제대로 알아보고자 하는 취지 중 하나

데코레이터 패턴

  • 기존의 것을 디펜던시로 가지고 덧되는 코드 패턴 : 무언가 추가될 때마다 코드를 전체적으로 다 변경(추가, 변형)하기보다 추가되는 부분의 코드만 만들어서 기존의 것과 합치는 코드(재활용)를 만들어낼 수 있음 - 자바 I/O 객체(예시 - BufferedReader)
    • 기존의 것과 같은 타입이 되기위해 같은 상위 타입(클래스)을 상속함 : 같은 상위 클래스를 구현하는 객체를 주입 받아서 본인의 기능과 함께 사용(꾸미는 개념)
    • 아래 연습해본 코드 : cost 부분은 전략패턴으로 바꿔서 관리하는 것이 더 나은 편이라고 생각하지만 지금은 간략하게 데코레이터 패턴에 대해서 연습하다보니 생각나는대로 코드를 짜본 것
/* BufferedReader */
public class BufferedReader implements Reader {
    private Reader in;

    public BuffredReader(Reader reader) {
        this(reader, defaultCharBufferSize);
    }

    public int read(char cbuf[], int off, int len) throws IOException {
        .
        .
        .

        int n = read1(cbuf, off, len);

        .
        .
        .        
    }

    private int read1(char[] cbuf, int off, int len) throws IOException {
        .
        .
        .
        if (len >= cb.length && markedChar <= UNMARKED && !skipLF) {
            return in.read(cbuf, off, len);
        }
    }
}

/* 연습 */
public abstract class Beverage {

    private int cost;

    public Beverage(int cost) {
        this.cost = cost;
    }

    public abstract int cost();

    public int getCost() {
        return cost;
    }

    public Beverage takeOut() {
        return new TakeOut(this);
    }
}

public class Blended extends Beverage {

    public Blended(int cost) {
        super(cost);
    }

    @Override
    public int cost() {
        return super.getCost();
    }
}

public class TakeOut extends Beverage {

    public TakeOut(Beverage beverage) {
        super(beverage.getCost());
    }

    @Override
    public int cost() {
        return (int) (super.getCost() * 0.5);
    }
}

데코레이터 패턴 - 생각되는 부분

  • 굳이 걑은 타입으로 만들어야한다면(동일한 인터페이스로 호출해야할 경우, 다형성) 상속을 하겠지만 그렇지않다면 의존성 주입만 해주고 그렇게 사용하면 되지 않을까?
  • 1가지 이상 덧되지 못한다고 생각함 : 벗겨내기도 번거롭다고 생각됨 - 여러 모듈을 하나의 기판에 붙이는 것도 아니고

알듯 말듯해서 다시 정리하는 입출력모델 blocking/non-blocking, sync/async

입출력(I/O)

  • 파일, 프로그램, 외부(네트워크 연결된, 예를 들어 외부 DBMS) 간의 데이터를 주고 받는 것을 말함

입출력 모델 두 그룹의 서로 다른 관심사

  • blocking/non-blocking : 함수(오픈 API, 시스템 호출 등)를 호출한 쪽에서의 호출했을 대 완료될 때까지 기다려야하거나 완료가 되지않았지만 return을 줘서 다음 처리를 할 수 있거나에 대한 관심
  • sync/async : 호출 당한 쪽에서 처리를 한 후에 호출한 쪽에서 할 것인지, 아니면 처리에 대한 신경을 쓰지 않도록 할 것인지에 대한 관심

왜 헷갈릴까

  • 비슷하게 동작하고 하나의 관심사만 관심가져야하는 것이 아니라 두 개의 모델을 합한 I/O 모델을 사용하고 있기때문이라고 생각됨 : non-blocking + async, blokcing + sync 처럼
    • 관련 이미지
    • 자바스크립트 : non-blocking + async 모델 사용 - 기다리지도 않고, 처리 이후 처리를 콜백으로 처리