어댑터 패턴 (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) 인스턴스를 만든 객체의 메소드 호출
'디자인패턴' 카테고리의 다른 글
[디자인패턴] Chapter 05. 싱글톤 패턴 (0) | 2022.11.05 |
---|---|
[디자인패턴] Chapter 03. 데코레이터 패턴 (0) | 2022.10.23 |