flagtags / OOP-study

0 stars 0 forks source link

7장 어댑터 패턴 #21

Open j03y14 opened 4 months ago

j03y14 commented 4 months ago

어댑터 패턴

어댑터: 인터페이스를 클라이언트에서 요구하는 형태로 적응시키는 역할

  1. 클라이언트에서 타깃 인터페이스로 메서드 호출을 해서 어댑터에 요청을 보낸다.
  2. 어댑터는 어댑티 인터페이스로 요청을 어댑티에 관한 메서드 호출로 변환한다.
  3. 클라이언트는 호출 결과를 받지만 중간에 어댑터가 있다는 사실을 모른다.

클라이언트에서 호출하는 부분을 타깃 인터페이스에 맞춰서 바꿔도 되지만 그러면 엄청나게 변경사항이 많아진다.

interface Duck {
    quack(): void;
    fly(): void;
}

class MallardDuck implements Duck {
    quack() {
        console.log('Quack');
    }

    fly() {
        console.log('I\'m flying');
    }
}

class Turkey {
    gobble() {
        console.log('Gobble gobble');
    }

    fly() {
        console.log('I\'m flying a short distance');
    }
}

class TurkeyAdapter implements Duck {
    turkey: Turkey;

    constructor(turkey: Turkey) {
        this.turkey = turkey;
    }

    quack() {
        this.turkey.gobble();
    }

    fly() {
        this.turkey.fly();
    }
}

const turkey = new Turkey();
const turkeyAdapter = new TurkeyAdapter(turkey);

turkeyAdapter.quack(); // turkeyAdapter는 Duck이다.

퍼사드 패턴

인터페이스를 단순하게 바꾸려고 인터페이스를 변경.

서브 시스템의 일련의 인터페이스를 통합 인터페이스로 묶어준다.

가장 중요한 점은 패턴의 용도

// example of facade pattern

class Light {
    on() {
        console.log('light on');
    }

    off() {
        console.log('light off');
    }
}

class Stereo {
    on() {
        console.log('stereo on');
    }

    off() {
        console.log('stereo off');
    }

    setCD() {
        console.log('stereo setCD');
    }

    setVolume(volume: number) {
        console.log(`stereo volume set to ${volume}`);
    }
}

class TV {
    on() {
        console.log('TV on');
    }

    off() {
        console.log('TV off');
    }
}

class HomeTheaterFacade {
    light: Light;
    stereo: Stereo;
    tv: TV;

    constructor(light: Light, stereo: Stereo, tv: TV) {
        this.light = light;
        this.stereo = stereo;
        this.tv = tv;
    }

    watchMovie() {
        this.light.off();
        this.stereo.on();
        this.stereo.setCD();
        this.stereo.setVolume(11);
        this.tv.on();
    }

    endMovie() {
        this.light.on();
        this.stereo.off();
        this.tv.off();
    }
}

❗ 디자인 원칙 6 - 절친에게만 이야기 한다.

의존관계가 복잡하게 얽혀서 시스템의 한 부분을 변경했을 때 다른 부분까지 줄줄이 고쳐야 하는 상황을 방지할 수 있다.

복잡한 의존 관계를 방지하는 방법?

kkirico commented 4 months ago

어댑터 패턴

플러그 어댑터 처럼 서로 다른 인터페이스를 변환해주는 역할을 한다.

interface Duck {
    quack: () => void;
    fly: () => void;
}

class MallardDuck implements Duck() {
    quack() {
        console.log('quack');
    }
    fly() {
        console.log('fly');
    }
}

interface Turkey {
    gobble: () => void;
    fly: () => void;
}

class WildTurkey implements Turkey() {
    gobble() {
        console.log('gobble');
    }
    fly() {
        console.log('fly shorter distance');
    }
}

class TurkeyAdapter implements Duck() {
    turkey;

    constructor(turkey: Turkey){
        this.turkey = turkey
    }

    quack() {
        this.gobble();
    }

    fly() {
        this.fly();
        this.fly();
        this.fly();
        this.fly();
    }
}

어댑터 패턴 정의

특정 클래스 인터페이스를 클라이언트에서 요구하는 다른 인터페이스로 변환합니다. 인터페이스가 호환되지 않아 같이 쓸 수 없었던 클래스를 사용할 수 있게 도와줍니다.

객체 구성을 사용합니다.

객체 어댑터는 구성을 사용하고 클래스 어댑터는 상속을 사용합니다.

구성을 사용하면 어댑티에 유연성이 확보됩니다.

상속을 사용하면 어댑터 객체는 한 종류만 만들면 됩니다.

퍼사드 패턴

하나 이상의 클래스 인터페이스를 깔끔하면서 효과적인 외관(파사드)로 덮어줍니다.

서브시스템 클래스의 기능을 사용할 수 있는 간단한 인터페이스를 제공한다.

진짜 작업은 서브시스템에 맡깁니다.

class HomeTheaterFacade() {
    amp;
    tuner;
    player;
    projector;
    lights;
    screen;
    popper;

    constructor(amp, tuner, player, projector, screen, lights, popper){
        this.amp = amp;
        this.tuner = tuner;
        //...
    }

    watchMovie(movie: string) {
        popper.on();
        popper.pop();
        lights.dim(0);
        screen.down();
        projector.on();
        projector.wideScreenMode();
        amp.on();
        amp.setStreamingPlayer(player);
        amp.setSurrounSound();
        amp.setVolume(5);
        player.on();
        player.play(movie);
    }

    endMovie() {
        popper.off();
        // ...
    } 
}

퍼사드에서 기능을 추가하기도합니다.

하나의 서브시스템에 여러개의 퍼사드가 존재할 수 있습니다.

클라이언트 구현과 복잡한 서브시스템을 분리할 수 있습니다.

어댑터 패턴과 퍼사드 패턴은 그 목적에 따라 분류할 수 있습니다.

객체 지향 원칙: 최소 지식 원칙

시스템을 디자인할 때 어떤 객체든 그 객체와 상호작용하는 클래스의 개수와 상호작용 방식에 주의를 기울여야 합니다.

아래 네개의 경우에만 허용하면 원칙을 지킬 수 있습니다.

메소드를 호출한 결과로 리턴 받은 객체에 들어있는 메소드를 호출하면, 다른객체의 일부분에 요청하게 됩니다. 이는 직접적으로 상호작용하는 객체가 늘어나는 결과를 초래합니다.

예시)

// station, thermometer
const getTemp = () => {
    thermometer = station.getThermometer();
    return thermometer.getTemapature();
}

// station
const getTemp = () => {
    return station.getTemparature();
}

파사드 패턴을 사용하면 클라이언트가 파사드 객체 하나에만 상호작용합니다.

kkirico commented 4 months ago

메소드를 생성하거나 인스턴스를 만든 객체

  1. 그 메소드에서 생성한 객체
    • 그 메소드에서 생성한 객체
  2. 인스턴스를 만든 객체
    • 파사드 패턴을 사용하는 client에서 서브시스템의 모든 객체의 인스턴스를 파사드 객체로 넘겨줘야하는데, 이때 생성을 위해 사용하는 서브시스템의 객체들은 최소 지식 원칙에 위배되지 않는다고 본다. 즉, 상호작용하지 않는다고 본다.
kkirico commented 4 months ago

최소 지식 원칙 객체 자신에 명시적으로 들어온 객체만 사용한다. 클래스가 복잡하게 얽혀서 변경이 일어났을 때 연쇄적으로 변경이 일어나는 문제를 방지할 수 있다 (부작용을 최소화 할 수 있다.)

station에서 나온 메소드는 station 한테 역할을 위임하므로 변경이 station에서만 일어날 수 있다.

온도계 예시에서, getThermometer에 변경이 일어나면, 위에 예시에서는 사용하는 쪽에서 수정이 불가피할 수 있다. 아래 예시에서는 station 내부 코드만 수정하면된다.

결국 최소지식 원칙의 네가지 경우는 상호작용 방식을 변경할 수 없는 네가지 종류이고, 나머지의 경우는 관계의 수정가능성이 있는 부분이라고 볼 수 있다.