Adaptor Pattern
말 그대로 어댑터(Adaptor)처럼 사용되는 디자인 패턴이다.
한국에서 전자기기를 사용하기위해 220V규격을 맞춰서 사용하는데, 그대로 110V 플러그로 사용할 수 없다. 이때에 변환젠더(Adaptor)를 사용하면 110V 규격에서도 사용가능하다.
이처럼 서로 호환성이 맞질않아 interface 사이를 잇는 bridge 역할을 수행하는 패턴
여기서 잠깐! - Adapter ? Adaptor ? 둘중 무엇일까?
https://englishforkorean.tistory.com/entry/Adapter-vs-Adaptor-%EB%9C%BB-%EC%B0%A8%EC%9D%B4
둘다 같은 표현으로 쓰인다고 한다. 필자는 Adaptor로 통일하여 표현하도록 하겠다.
전체적인 구조
Client
Third Party Library나 외부시스템을 사용하려는 쪽이다.
Adaptee
Third Party Library나 외부시스템을 의미한다.
Target Interface
Adaptor가 구현(implements)하는 interface이다. Client는 target interface를 통해 adaptee인 third party library를 사용하게 된다.
Adaptor
Client와 adaptee중간에서 호환성이 없는 둘을 연결시켜주는 역할을 담당한다. Client는 adaptor에 요청을 보낸다. adaptor는 client의 요청을 adaptee가 이해할 수 있는 방법으로 전달하고, 처리는 adaptee에서 이루어진다.
내용 글 출처: https://yaboong.github.io/design-pattern/2018/10/15/adapter-pattern/
아래의 코드들을 확인해보자
Example1
더보기
// Print.java
public interface Print {
public abstract void printWeak();
public abstract void printStrong();
}
// Target Interface - 메소드에 대한 구현이 아닌 정의만 되어있다.
// Banner.java
public class Banner {
private String string;
public Banner(String string) {
this.string = string;
}
public void showWithParen() {
System.out.println("(" + string + ")");
}
public void showWithAster() {
System.out.println("*" + string + "*");
}
}
// Adaptee - 실제 구동되는 기능이 담긴 Class
// PrintBanner.java
public class PrintBanner extends Banner implements Print {
public PrintBanner(String string) {
super(string);
}
public void printWeak() {
showWithParen();
}
public void printStrong() {
showWithAster();
}
}
// Adaptor - Print의 interface를 가져와 Banner의 기능을 묶어준다.(Class를 주입한다)
// Main.java
public class Main {
public static void main(String[] args) {
Print p = new PrintBanner("Hello");
p.printWeak();
p.printStrong();
}
}
// Client - PrintBanner Class를 실행했지만 기능적으로 Banner Class의 기능이 실행됩니다.
// Output:
// (Hello)
// *Hello*
구조 | Example1 Class |
Client | Main |
Adaptee | Banner |
Target Interface | |
Adaptor | PrintBanner |
Example 2
더보기
class LegacyLine {
public void draw(int x1, int y1, int x2, int y2) {
System.out.println("line from (" + x1 + ',' + y1 + ") to (" + x2 + ','
+ y2 + ')');
}
}
class LegacyRectangle {
public void draw(int x, int y, int w, int h) {
System.out.println("rectangle at (" + x + ',' + y + ") with width " + w
+ " and height " + h);
}
}
public class AdapterDemo {
public static void main(String[] args) {
Object[] shapes = {
new LegacyLine(), new LegacyRectangle()
};
int x1 = 10, y1 = 20;
int x2 = 30, y2 = 60;
for (Object obj : shapes) {
if (LegacyLine.class.isInstance(obj)) {
LegacyLine.class.cast(obj).draw(x1, y1, x2, y2);
} else if (LegacyRectangle.class.isInstance(obj)) {
LegacyRectangle.class.cast(obj).draw(Math.min(x1, x2), Math.min(y1, y2),
Math.abs(x2 - x1), Math.abs(y2 - y1));
}
}
}
}
// Output:
// line from (10,20) to (30,60)
// rectangle at (10,20) with width 20 and height 40
Example3
더보기
class LegacyLine{
public void draw(int x1, int y1, int x2, int y2){
System.out.println("line from (" + x1 + ',' + y1 + ") to (" + x2 + ','
+ y2 + ')');
}
}
class LegacyRectangle{
public void draw(int x, int y, int w, int h){
System.out.println("rectangle at (" + x + ',' + y + ") with width " + w
+ " and height " + h);
}
}
interface Shape{
void draw(int x1, int y1, int x2, int y2);
}
class Line implements Shape{
private LegacyLine adaptee = new LegacyLine();
public void draw(int x1, int y1, int x2, int y2){
adaptee.draw(x1, y1, x2, y2);
}
}
class Rectangle implements Shape{
private LegacyRectangle adaptee = new LegacyRectangle();
public void draw(int x1, int y1, int x2, int y2){
adaptee.draw(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x2 - x1),
Math.abs(y2 - y1));
}
}
public class AdapterDemo2{
public static void main(String[] args){
Shape[] shapes = {
new Line(), new Rectangle()
};
int x1 = 10, y1 = 20;
int x2 = 30, y2 = 60;
for (Shape shape : shapes)
shape.draw(x1, y1, x2, y2);
}
}
// Output:
// line from (10,20) to (30,60)
// rectangle at (10,20) with width 20 and height 40
Example2와 3의 차이가 보이나요?
- 2에서 3로 Adaptor pattern을 적용할 수 있습니다.
- Example2의 LegacyLine에서 기능적인 부분을 수정하고자 한다면 Client에서도 오류없이 잘 돌아가야하기 때문에 수정된 부분을 LegacyRectangle도 다시 정의해주어야 합니다. 이는 커다란 시스템에서 비효율적으로 작용할 것입니다. 따라서 Example3와 같이 Adaptor Pattern을 적용해주었습니다.
Example4
더보기
interface Bird
{
public void fly();
public void makeSound();
}
interface ToyDuck
{
public void squeak();
}
class BirdAdapter implements ToyDuck
{
Bird bird;
public BirdAdapter(Bird bird)
{
this.bird = bird;
}
public void squeak()
{
bird.makeSound();
}
}
class PlasticToyDuck implements ToyDuck
{
public void squeak()
{
System.out.println("Squeak");
}
}
class Sparrow implements Bird
{
public void fly()
{
System.out.println("Flying");
}
public void makeSound()
{
System.out.println("Chirp Chirp");
}
}
class Main
{
public static void main(String args[])
{
Sparrow sparrow = new Sparrow();
ToyDuck toyDuck = new PlasticToyDuck();
ToyDuck birdAdapter = new BirdAdapter(sparrow);
System.out.println("Sparrow...");
sparrow.fly();
sparrow.makeSound();
System.out.println("ToyDuck...");
toyDuck.squeak();
System.out.println("BirdAdapter...");
birdAdapter.squeak();
}
}
// Output:
// Sparrow...
// Flying
// Chirp Chirp
// ToyDuck...
// Squeak
// BirdAdapter...
// Chirp Chirp
Adaptor Pattern 효과
- 각 파트별로 기능을 나누어 Adaptee를 감싸고, Target interface만을 client에게 드러냅니다.
- Target interface를 구현하여 client가 예상한 interface가 될 수 있도록 adaptee를
간접적으로
변경합니다. - Adaptee가 원하는 방식으로 client의 요청을 변경할 수 있습니다.
- 호환되지 않는 interface와 adaptee를 함께 사용가능합니다.
- Code maintainability(유지보수성)를 향상시킬 수 있습니다.
정리
- A를 B에서도 사용할 수 있게 B처럼 포장하는 패턴이다.
- 사용하는 시스템의 인터페이스가 현재 호환되지 않는다고 해서 시스템 전체를 변경하는 것이 아닌 이런 방법도 존재한다는 것을 알 수 있었다.