본문 바로가기

디자인패턴

[디자인패턴] Chapter 07. 어댑터 패턴과 퍼사드 패턴

어댑터 패턴 (Adapter Pattern)

특정 클래스 인터페이스를 클라이언트에서 요구하는 다른 인터페이스로 변환한다.

인터페이스가 호환되지 않아 같이 쓸 수 없었던 클래스를 사용할 수 있게 도와준다.

 

객체지향 어댑터

- 예) 어떤 소프트웨어 시스템에 새로운 업체에서 제공한 클래스 라이브러리를 사용해야 하는데,

그 업체에서 사용하는 인터페이스가 기존에 사용하던 인터페이스와 다른 경우

사용 가능한 인터페이스로 한번 감싸서 사용한다.

 

오리 어댑터

1.  어댑터 적용 전

IDuck 인터페이스를 구현하는 MallardDuck

ITurkey 인터페이스를 구현하는 WildTurkey

// 오리 인터페이스
internal interface IDuck
{
    public void Quack();
    public void Fly();
}

internal class MallardDuck : IDuck
{
    public void Fly()
    {
        Console.WriteLine("날고 있어요!");
    }

    public void Quack()
    {
        Console.WriteLine("꽥");
    }
}


// 칠면조 인터페이스
internal interface ITurkey
{
    public void Gobble();
    public void Fly();
}

internal class WildTurkey : ITurkey
{
    public void Fly()
    {
        Console.WriteLine("짧은 거리를 날고 있어요!");
    }

    public void Gobble()
    {
        Console.WriteLine("골골");
    }
}

 

MallardDuck과 WildTurkey는 구현하는 인터페이스가 달라서 각각의 메소드를 호출해줘야 한다.

IDuck duck = new MallardDuck();
ITurkey turkey = new WildTurkey();

Console.WriteLine("칠면조가 말하길");
turkey.Gobble();
turkey.Fly();

Console.WriteLine("오리가 말하길");
duck.Quack();
duck.Fly();

1.  어댑터 적용 후

IDuck을 구현하는 TurkeyAdapter 클래스를 만든다.

ITurkey 객체를 받아, 칠면조의 메소드를 오리 인터페이스에 맞게 호출한다.

internal class TurkeyAdapter : IDuck
{
    ITurkey turkey;

    public TurkeyAdapter(ITurkey turkey)
    {
        this.turkey = turkey;
    }

    public void Fly()
    {
        for (int i = 0; i < 5; i++)
            turkey.Fly();
    }

    public void Quack()
    {
        turkey.Gobble();
    }
}

 

칠면조 객체를 생성한 후, 칠면조 어댑터로 감싸준다.

IDuck turkeyAdapter = new TurkeyAdapter(turkey);

이제 turkeyAdapter는 오리가 되었다.

IDuck duck = new MallardDuck();

ITurkey turkey = new WildTurkey();
IDuck turkeyAdapter = new TurkeyAdapter(turkey);    // ITurkey => IDuck

Console.WriteLine("칠면조가 말하길");
turkey.Gobble();
turkey.Fly();

Console.WriteLine("오리가 말하길");
TestDuck(duck);

Console.WriteLine("칠면조 어댑터가 말하길");
TestDuck(turkeyAdapter);

void TestDuck(IDuck duck)
{
    duck.Quack();
    duck.Fly();
}

따라서 IDuck 객체를 받아서 공통으로 처리하는 TestDuck 메소드를, 칠면조도 사용할 수 있게 되었다.

 

하나의 인터페이스로 묶어서 한번에 어떤 작업을 처리하고자 하는 경우 특히 유용할 것 같다.

List<IDuck> ducks = new List<IDuck>();
ducks.Add(new MallardDuck());
ducks.Add(new TurkeyAdapter(new WildTurkey()));

foreach (IDuck duck in ducks)
    TestDuck(duck);

 

여기에서 Turkey 객체는 어댑티(Adaptee) 인터페이스라고 한다. (=> 감싸는 대상)

 

장점

- 클라이언트, 즉 사용하는 쪽에서는 어댑터에 대해 알 필요가 없다. 타깃 인터페이스만 알면 된다.

 

클래스 어댑터

- 다중 상속을 받아서 구현한다. 구성을 사용하는 대신 어댑터를 어댑티와 타깃 클래스의 서브 클래스로 만든다.

- 상속을 받기 때문에 어댑티의 행동을 오버라이드할 수 있다.

위 오리 어댑터 예제를 클래스 어댑터로 사용하면 아래처럼 구현할 수 있을 것 같다.

클래스 어댑터는 WildTurkey를 바로 상속받기 때문에, 생성자로 객체를 받지 않는다.

internal class ClassAdapter : WildTurkey, IDuck
{
    public void Quack()
    {
        Gobble();
    }
}


// 사용

Console.WriteLine("칠면조 클래스 어댑터가 말하길");
IDuck classAdapter = new ClassAdapter();
TestDuck(classAdapter);

void TestDuck(IDuck duck)
{
    duck.Quack();
    duck.Fly();
}

 

 

퍼사드 패턴 (Facade Pattern)

퍼사드 패턴은 다른 패턴보다 상당히 단순하다.

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

또한 고수준 인터페이스도 정의하므로 서브시스템을 더 편리하게 사용할 수 있다.

즉, 필요한 기능들을 한번에 처리하기 위해 통합하는 패턴이다.

 

홈시어터 퍼사드 만들기

internal class HomeTheaterFacade
{
    Amplifier amp;
    Tuner tuner;
    StreamingPlayer player;
    CdPlayer cd;
    Projector projector;
    TheaterLights lights;
    Screen screen;
    PopcornPopper popper;

    public HomeTheaterFacade(Amplifier amp, Tuner tuner, StreamingPlayer player, Projector projector, Screen screen, TheaterLights lights, PopcornPopper popper)
    {
        this.amp = amp;
        this.tuner = tuner;
        this.player = player;
        this.projector = projector;
        this.screen = screen;
        this.lights = lights;
        this.popper = popper;
    }

    public void WatchMovie(string movie)
    {
        Console.WriteLine("Get ready to watch a movie...");
        popper.On();
        popper.Pop();
        lights.Dim(10);
        screen.Down();
        projector.On();
        projector.WideScreenMode();
        amp.On();
        amp.SetStreamingPlayer(player);
        amp.SetSurroundSound();
        amp.SetVolume(5);
        player.On();
        player.Play(movie);
    }

    public void EndMovie()
    {
        Console.WriteLine("Shutting movie theater down...");
        popper.Off();
        lights.On();
        screen.Up();
        projector.Off();
        amp.Off();
        player.Stop();
        player.Off();
    }

    public void ListenToRadio(double frequency)
    {
        Console.WriteLine("Tuning in the airwaves...");
        tuner.On();
        tuner.SetFrequency(frequency);
        amp.On();
        amp.SetVolume(5);
        amp.SetTuner(tuner);
    }

    public void EndRadio()
    {
        Console.WriteLine("Shutting down the tuner...");
        tuner.Off();
        amp.Off();
    }
}

1. HomeTheaterFacade 를 생성할 때 필요한 객체들을 넘겨준다.

2. 필요한 작업들을 묶어서 하나의 메소드에서 순서대로 호출한다.

 

 

어댑터와 퍼사드의 차이

어댑터 : 인터페이스를 클라이언트에서 필요로 하는 인터페이스로 변환하는 용도

퍼사드 : 어떤 서브 시스템에 대한 간단한 인터페이스를 제공하는 용도

 

 

디자인 원칙

최소 지식 원칙 (Principle of Least Knowledge)

: 객체 사이의 상호 작용은 될 수 있으면 아주 가까운 '친구' 사이에서만 허용해야 한다.

(= 데메테르의 법칙)

 

가이드라인

- 객체 자체만 사용

- 메소드에 매개변수로 전달된 객체만 사용

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

- 객체에 속하는 구성요소만 사용

 

예)

// A. 원칙을 따르지 않은 경우
public float GetTemp()
{
    Thermometer thermometer = station.GetThermometer();
    return thermometer.GetTemperature();
}

// B. 원칙을 따르는 경우
public float GetTemp()
{
    return station.GetTemperature();
}

 

 

A

객체에 속하는 구성요소인 station을 사용한다. (O)

station으로부터 thermometer 객체를 받아, 그 객체의 GetTemperature()를 직접 호출한다. (X)

 

 

최소 지식 원칙을 따르는 예제

public class Car
{
    Engine engine;
    // 기타 인스턴스 변수

    public Car()
    {
        // 엔진 초기화 등을 처리
    }
    
    public void Start(Key key)
    {
        Doors doors = new Doors();
        bool authorized = key.Turns();
        if (authorized)
        {
            engine.Start();
            UpdateDashboardDisplay();
            doors.Lock();
        }
    }

    public void UpdateDashboardDisplay()
    {
        // 디스플레이 갱신
    }
}

 

가이드라인

1) 객체 자체만 사용

2) 메소드에 매개변수로 전달된 객체만 사용

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

4) 객체에 속하는 구성요소만 사용

 

1. Doors doors = new Doors(); ==> (3) 인스턴스를 만든 객체

2. bool authorized = key.Turns(); ==> (2) 메소드에 매개변수로 전달된 객체 사용

3. engine.Start(); ==> (4) 객체에 속하는 구성요소 사용

4. UpdateDashboardDisplay() ==> (1) 객체 내의 메소드 호출

5. doors.Lock(); ==> (3) 인스턴스를 만든 객체의 메소드 호출