플라이웨이트 패턴이란?

플라이웨이트 패턴은 많은 수의 유사한 객체를 효율적으로 관리하기 위한 구조적 디자인 패턴입니다. 객체의 내부 상태를 공유함으로써 메모리 사용을 최적화합니다. 코드를 보면서 이해해봅시다.

 

 

재사용 가능 여부

플라이웨이트 패턴은 자원의 재사용 여부를 먼저 따져봐야합니다. 같은 자원을 공유해도 문제가 없는 데이터를 재사용함으로써 메모리를 절약할 수 있으니까요.

예제로 전쟁시뮬레이션 게임을 만든다고 가정해보겠습니다. 하나의 객체를 만들때 텍스쳐, 사운드, 폴리곤, 객체의 좌표가 사용된다고 해보겠습니다.

 

 

 

플라이웨이트 패턴 적용 전

먼저 메모리 사용량을 표시하기 위해 메모리 클래스를 하나 만들겠습니다.

public class Memory {

    public static int size = 0;

    public static void print() {
        System.out.println("메모리 사용량 : " + size + "MB");
    }
}

 

 

public class Soldier {

    private final int size = 10;
    private final String texture;
    private final String sound;
    private final int polygons;

    private double positionX;
    private double positionY;

    public Soldier(String texture, String sound, int polygons, double positionX, double positionY) {
        this.texture = texture;
        this.sound = sound;
        this.polygons = polygons;
        this.positionX = positionX;
        this.positionY = positionY;

        // 메모리 사용량 증가
        Memory.size += size;
    }

}

 

public class Field {

    // 지형 타일 크기
    public static final int CANVAS_SIZE = 1000;

    public void render(String texture, String sound, int polygons, double positionX, double positionY) {
        Soldier soldier = new Soldier(texture, sound, polygons, positionX, positionY);
        System.out.println("병사 생성 : x : " + positionX + ", " + " y : " + positionY);
    }
}

 

 

    public static void main(String[] args) {
        Field field = new Field();

        for (int i = 0; i < 1000; i++) {
            field.render(
                "robe.png",
                "solider.wav",
                200,
                Field.CANVAS_SIZE * Math.random(),
                Field.CANVAS_SIZE * Math.random()
            );
        }

        Memory.print(); // 메모리 사용량 : 10000MB
    }

 

Solider 1000명을 Field에 rendering하는 코드를 간단하게 작성해보았습니다. 이때 무려 메모리를 10000mb나 사용되었네요. 만약 병사의 수가 증가한다면 기하급수적으로 메모리를 사용할 것입니다. 이 코드를 플라이웨이트 패턴을 이용해 메모리 사용량을 줄여보겠습니다.

 

 

플라이웨이트 패턴 적용 후

public class Soldier {

    private final int size = 2;

    private SoliderModel model;
    private double positionX;
    private double positionY;

    public Soldier(SoliderModel model, double positionX, double positionY) {
        this.model = model;
        this.positionX = positionX;
        this.positionY = positionY;

        // 메모리 사용량 증가
        Memory.size += size;
    }

}
public class SoliderModel {

    private final int size = 8;
    private final String texture;
    private final String sound;
    private final int polygons;

    public SoliderModel(String texture, String sound, int polygons) {
        this.texture = texture;
        this.sound = sound;
        this.polygons = polygons;

        Memory.size += size;
    }

}

 

Solider에서 재사용이 가능한 텍스쳐, 사운드, 폴리곤의 수를 SoliderModel 클래스로 새로 만들었습니다.

메모리 사용량은 SoliderModel = 8, Solider = 2 로 분배되었습니다.

 

public class SoliderModelFactory {

    private static final Map<String, SoliderModel> cache = new HashMap<>();

    public static SoliderModel getModel(String texture, String sound, int polygons) {
        String key = texture + sound + polygons;
        return cache.computeIfAbsent(key, k -> new SoliderModel(texture, sound, polygons));
    }
    
    public static void clear() {
        cache.clear();
    }
}

 

Factory를 만들고 한번 만들었던 모델데이터는 이곳에서 재사용됩니다.

 

public class Field {

    // 지형 타일 크기
    public static final int CANVAS_SIZE = 1000;

    public void render(String texture, String sound, int polygons, double positionX, double positionY) {
        SoliderModel model = SoliderModelFactory.getModel(texture, sound, polygons);
        Soldier soldier = new Soldier(model, positionX, positionY);
        System.out.println("병사 생성 : x : " + positionX + ", " + " y : " + positionY);
    }
}

필드에서 SoliderModelFactory.getModel 로 모델데이터를 가져와 Solider를 만들어 렌더링합니다.

 

    public static void main(String[] args) {
        Field field = new Field();

        for (int i = 0; i < 1000; i++) {
            field.render(
                "robe.png",
                "solider.wav",
                200,
                Field.CANVAS_SIZE * Math.random(),
                Field.CANVAS_SIZE * Math.random()
            );
        }

        Memory.print(); // 메모리 사용량 : 2008MB
        SoliderModelFactory.clear();
    }

메모리 사용량을 출력해본 결과 2008MB 가 나왔습니다. 플라이웨이트 적용전보다 무려 80% 감소되었습니다. 반복이 많아질 수록 경량화될겁니다.

 

 

주의점

렌더링이 끝났다면 factory를 초기화해줄 필요가 있습니다. 관리되고있는 인스턴스는 GC에서 제거하지 않기 때문에 메모리 누수가 발생할 수 있습니다.  참고로 이런 캐싱방식은 StringConstantPool과 같은 개념이라고 할 수 있습니다.

 

 

[JAVA] String의 불변성(immutability)

들어가며JAVA를 처음 접할 때, String은 불변객체라고 배우고 지나갔습니다. 하지만 실제 String을 다루다보면 어째서 불변객체인지 의문이 들곤합니다.  String str = "apple"; str = "banana";왜냐하면, 재할

tmd8633.tistory.com

String Constant Pool과 String 불변성에 대해서 더 알고싶다면 해당 게시물을 읽어보시는걸 추천드립니다.

 

 

마치며

플라이웨이트 패턴은 메모리 사용량을 크게 줄일 수 있는 강력한 도구입니다. 하지만 모든 상황에서 적합한 것은 아니며, 다음과 같은 상황에서 고려해볼 만합니다:

  1. 애플리케이션에서 많은 수의 유사한 객체를 사용할 때
  2. 객체의 상태를 내부와 외부로 명확히 구분할 수 있을 때
  3. 메모리 사용량이 중요한 제약 조건일 때

 

 

 

 

 

복합 패턴 개요

복합 패턴은 객체들을 트리 구조로 구성하여 부분-전체 계층구조를 구현하는 패턴입니다. 이 패턴을 사용하면 클라이언트에서 개별 객체와 복합 객체를 동일하게 다룰 수 있습니다. 오늘은 이 패턴에 대해서 공부해보겠습니다.

출처 : https://ko.wikipedia.org/wiki/%EC%BB%B4%ED%8F%AC%EC%A7%80%ED%8A%B8_%ED%8C%A8%ED%84%B4

 

 

 

뭐하는 패턴이지?

위의 구조를 보면 당최 뭐하는 패턴인지 감이 안잡힐 수 있습니다. 간단하게 말하면 복합객체와 단일객체를 동일한 타입을 취급해서 단순화시키는 것인데요. 예를들어 폴더 - 파일의 구조를 가진다고 했을때, 폴더와 파일을 하나의 객체로 동일하게 취급해서 구조를 단순화 시키는 것입니다. 폴더안에는 파일이 들어갈 수 있고, 폴더안에는 폴더가 들어갈 수 있는 구조를 생각했을 때 이걸 효과적으로 단일화 시킬 수 있다는 뜻이죠. 코드를 보면서 이해해보도록 하겠습니다.

 

 

 

복합 패턴의 구조

위의 사진에서 Leaf는 단일객체, Composite는 복합객체를 뜻합니다. 우리는 Leaf -> 파일, Composite -> 폴더로 치환해서 코드를 구현해보겠습니다.

 

public interface FileComponent {

    int getSize();
    String getTitle();

}

 

public class File implements FileComponent {

    private final String title;
    private final int size;

    public File(String title, int size) {
        this.title = title;
        this.size = size;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public String getTitle() {
        return title;
    }
}

 

public class Folder implements FileComponent {

    private final String title;
    private final List<FileComponent> files = new ArrayList<>();

    public Folder(String title) {
        this.title = title;
    }

    @Override
    public int getSize() {
        int size = 0;
        for (FileComponent file : files) {
            size += file.getSize();
        }
        return size;
    }

    @Override
    public String getTitle() {
        return title;
    }

    public void add(FileComponent file) {
        files.add(file);
    }

    public void remove(FileComponent file) {
        files.remove(file);
    }

}

 

FileComponent interface를 만들고 파일과 폴더에서 구현했습니다.

 

 

    public static void main(String[] args) {
        Folder folder1 = new Folder("폴더1");
        folder1.add(new File("파일1", 300));

        Folder folder2 = new Folder("폴더2");
        folder2.add(new File("파일2", 1000));
        folder2.add(new File("파일3", 500));


        Folder folder3 = new Folder("폴더2");


        folder1.add(folder2);
        folder1.add(folder3);

        System.out.println("folder1.getTitle() = " + folder1.getTitle()); // 폴더1
        System.out.println("folder2.getSize() = " + folder1.getSize()); // 1800
    }

 

폴더1을 만들고 파일1을 넣었습니다.

폴더2를 만들고 파일2와 파일3을 넣었습니다.

그리고 아무것도 들어있지않은 폴더3을 만들었습니다.

 

마지막으로 폴더1에 폴더2와 폴더3을 넣었습니다.

 

이제 folder1의 title과 size를 출력해보면 내부에 들어있는 모든 파일의 크기가 출력됩니다.

 

 

 

복합 패턴의 장단점

장점

  1. 클라이언트 코드를 단순화
  2. 새로운 종류의 구성요소를 쉽게 추가
  3. 전체-부분 관계를 표현하기 용이

단점

  1. 설계가 지나치게 일반화될 수 있음
  2. 제약사항을 추가하기 어려움

 

 

 

복합패턴을 이용해 메뉴시스템 구현

복합패턴에 대해서 배웠으니 이 패턴을 활용해서 메뉴시스템을 구현해보겠습니다.

 

public abstract class MenuComponent {

    protected String name;
    protected String description;

    public MenuComponent(String name, String description) {
        this.name = name;
        this.description = description;
    }

    public abstract void print();
}
public class MenuItem extends MenuComponent {

    private int price;

    public MenuItem(String name, String description, int price) {
        super(name, description);
        this.price = price;
    }

    @Override
    public void print() {
        System.out.println(name + ": " + description + " - " + price + "원");
    }
}
public class Menu extends MenuComponent {
    
    private List<MenuComponent> menuComponents = new ArrayList<>();

    public Menu(String name, String description) {
        super(name, description);
    }

    public void add(MenuComponent menuComponent) {
        menuComponents.add(menuComponent);
    }

    @Override
    public void print() {
        System.out.println("\n" + name + ": " + description);
        System.out.println("--------------------");

        for (MenuComponent menuComponent : menuComponents) {
            menuComponent.print();
        }
    }
}

 

폴더 - 파일 구조와 동일하게 구현했습니다. 조금 다른게 있다면 interface -> abstract가 변경된게 있습니다만 무시해도됩니다.

 

    public static void main(String[] args) {
        // 메인 메뉴
        Menu mainMenu = new Menu("메인 메뉴", "메뉴를 선택해주세요");

        // 커피 메뉴
        Menu coffeeMenu = new Menu("커피", "커피를 선택해주세요");
        coffeeMenu.add(new MenuItem("아메리카노", "진한 에스프레소의 맛과 향을 부드럽게 즐길 수 있는 아메리칸 스타일의 커피입니다.", 4500));
        coffeeMenu.add(new MenuItem("카메라떼", "진한 에스프레소에 우유를 넣어 풍부한 커피향을 부드럽게 즐실 수 있습니다.", 5200));

        // 음식 메뉴
        Menu foodMenu = new Menu("음식", "커피와 함께 즐길 음식을 선택해주세요");
        foodMenu.add(new MenuItem("크로크무슈", "햄과 치즈를 차곡차곡 쌓아 올려 만든 프랑스식 샌드위치로 사르르 녹아내린 모짜렐라 치즈가 입안 가득 풍성한 맛", 4500));
        foodMenu.add(new MenuItem("페스토 햄 파니니", "터키햄과 모짜렐라치즈를 두둑하게 채운 대중적인 파니니", 6500));

        // 메인메뉴에 커피, 음식메뉴를 추가
        mainMenu.add(coffeeMenu);
        mainMenu.add(foodMenu);

        // 전체 메뉴 출력
        mainMenu.print();
    }
메인 메뉴: 메뉴를 선택해주세요
--------------------

커피: 커피를 선택해주세요
--------------------
아메리카노: 진한 에스프레소의 맛과 향을 부드럽게 즐길 수 있는 아메리칸 스타일의 커피입니다. - 4500원
카메라떼: 진한 에스프레소에 우유를 넣어 풍부한 커피향을 부드럽게 즐실 수 있습니다. - 5200원

음식: 커피와 함께 즐길 음식을 선택해주세요
--------------------
크로크무슈: 햄과 치즈를 차곡차곡 쌓아 올려 만든 프랑스식 샌드위치로 사르르 녹아내린 모짜렐라 치즈가 입안 가득 풍성한 맛 - 4500원
페스토 햄 파니니: 터키햄과 모짜렐라치즈를 두둑하게 채운 대중적인 파니니 - 6500원

 

출력결과입니다.

 

 

마치며

복합 패턴은 트리 구조의 객체 구성을 다룰 때 매우 유용한 패턴입니다. 특히 전체-부분 관계를 표현해야 하는 상황에서 강력한 도구가 될 수 있습니다. 하지만 설계가 지나치게 일반화될 수 있다는 점을 주의해야 하며, 실제 요구사항에 맞게 적절히 제약을 추가하는 것이 중요합니다.

퍼사드 패턴

퍼사드 패턴은 복잡한 클래스들을 편하게 사용하기위해 인터페이스를 구성하는 구조 패턴입니다.

 

 

 

예제

IOT 를 조작하는 컨트롤러를 만든다고 가정해보겠습니다.

컨트롤러는 일어날때 커튼을 열고, 전등을 키고, 스피커에서 음악이 재생되도록 하고싶고,

집을 떠날때는 전등을 끄고, 스키퍼에서 음악이 멈추도록 하고싶습니다.

 

public class Remote {

    private final Lights lights;
    private final Curtains curtains;
    private final Speaker speaker;

    public Remote(Lights lights, Curtains curtains, Speaker speaker) {
        this.lights = lights;
        this.curtains = curtains;
        this.speaker = speaker;
    }

    public void wakeUp() {
        lights.on();
        curtains.open();
        speaker.musicStart();
    }

    public void leaveHome() {
        lights.off();
        speaker.musicStop();
    }
    
}
public class Lights {

    public void on() { ... }
    public void off() { ... }
    
}

public class Curtains {

    public void open() { ... }
    public void close() { ... }
    
}

public class Speaker {

    public void musicStart() { ... }
    public void musicStop() { ... }
    
}

 

이렇게 작성한다면 Remote를 사용하는 사용자는 내부 클래스의 구현에 대해서 알 필요가 없어지고, 내부 클래스의 수정이 일어나도 Remote 코드만 수정하면 되므로 유연성이 향상됩니다.

 

 

이 퍼사드 패턴은 디자인패턴에 대해서 알지 못하더라도 그냥 자연스럽게 만들어지는 패턴같습니다. 쉽고 유용한 패턴이니 잘 적용해보시기 바랍니다.

데코레이터 패턴

데코레이터 패턴은 객체를 동적으로 기능을 확장하거나 추가할 수 있는 패턴입니다. 특징으로는 객체를 감싸는 방식으로 기능을 추가하는건데요. 말로 설명하는 것보다 예제를 통해 배워보도록 하겠습니다.

 

 

 

예제

커피를 예로 들어보겠습니다.

커피의 종류는 아메리카노, 카페라떼, 등등이 있습니다.

이 커피들에 설탕이나 샷을 추가할 수 있다고 했을때, 어떻게 구현해야 할까요?

 

커피와 추가메뉴를 나누고 거기에서 커피객체에 메뉴를 추가하는 식으로 구현할 수 있을것입니다. 그러나 오늘은 데코레이터 패턴을 사용해상속을 통한 확장이 아닌 객체를 감싸는 방식으로 확장을 해보겠습니다.

 

전체적인 구조는 다음과 같습니다.

 

 

 

Coffee

public interface Coffee {

    String getDescription();
    int getCost();
    
}
public class Americano implements Coffee {

    @Override
    public String getDescription() {
        return "아메리카노";
    }

    @Override
    public int getCost() {
        return 5000;
    }
}

 

Coffee에는 메뉴의 이름을 반환하는 getDescription가 가격을 반환하는 getCost()가 있습니다.

Coffee를 상속받은 Americano를 구현했습니다.

 

 

 

CoffeeDecorator

public abstract class CoffeeDecorator implements Coffee {

    private final Coffee coffee;

    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }

    @Override
    public String getDescription() {
        return coffee.getDescription();
    }

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

}

 

public class ShotDecorator extends CoffeeDecorator {

    public ShotDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ", 샷 추가";
    }

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

}

public class SugarDecorator extends CoffeeDecorator {
    
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ", 설탕 추가";
    }

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

}

 

CoffeeDecorator를 상속받아 커피에 추가할 수 있는 옵션인 설탕과 샷 객체를 구현했습니다.

CoffeeDecorator의 구현객체를 보면 멤버변수에 Coffee를 가지고있습니다. 추가옵션은 Coffee가 반드시 필요하기 때문입니다.

 

실행결과

    public static void main(String[] args) {
        Coffee coffee = new SugarDecorator(new ShotDecorator(new Americano()));

        System.out.println(coffee.getDescription());
        System.out.println(coffee.getCost());

        // 아메리카노, 샷 추가, 설탕 추가
        // 5800
    }

 

coffee 변수안에 Decorator를 사용해서 추가옵션을 넣어 줄 수 있었습니다.

이렇게 하면 기존코드를 수정하거나 변경없이도 추가 확장할 수 있습니다.

 

여기서 살짝 아쉬운부분은 CoffeeDecorator에서 Coffee를 이미 Override를 했기때문에 Decorator를 구현할때 Override를 강제하기 않는다는것이었는데요. 그래서 강제할 수 있도록 약간 더 수정해보았습니다.

 

public abstract class CoffeeDecorator implements Coffee {

    private final Coffee coffee;

    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }

    @Override
    public String getDescription() {
        return coffee.getDescription() + getExtra();
    }

    @Override
    public int getCost() {
        return coffee.getCost() + getExtraCost();
    }

    abstract String getExtra();
    abstract int getExtraCost();
    
}

 

public class ShotDecorator extends CoffeeDecorator {

    public ShotDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    String getExtra() {
        return ", 샷 추가";
    }

    @Override
    int getExtraCost() {
        return 500;
    }
}

public class SugarDecorator extends CoffeeDecorator {
    
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    String getExtra() {
        return ", 설탕 추가";
    }

    @Override
    int getExtraCost() {
        return 300;
    }
}

 

CoffeeDecorator에서 getExtra()와 getExtraCost()를 추상화하여 상속받은 클래스에게 구현을 강제하게 했습니다.

이렇게 하니까 더 코드가 깔끔해진것 같습니다.

 

이 데코레이터 패턴은 유연하게 확장이 가능하고, 책임을 분리하고, DIP를 준수하는 등 좋은 장점들이 많으나 코드 복잡성이 증가하고, 순서에 의존하는 단점도 존재합니다.

브릿지패턴

브릿지패턴은 객체의 확장성을 높히기 위한 디자인패턴입니다. 객체를 구현클래스와 추상클래스로 분리하고 추상클래스에서 확장해가는 패턴입니다. 오늘은 브릿지 패턴에 대해서 최대한 쉽게 알아보겠습니다.

 

 

 

 

 

예제

 

리모콘과 디바이스의 관계를 보겠습니다.

리모콘은 어떤 디바이스의 종류가 오든 상관없습니다. 그저 전원을 키거나 끄는 역활을 할 뿐입니다.

디바이스는 어떤 리모콘이 오든 상관없습니다. 신호가 오면 전원을 키거나 끄도록 하면 됩니다.

 

여기서 리모콘은 디바이스를 의존하고있어야 어떤 디바이스에 신호를 보낼 지 결정할 수 있습니다. 따라서 리모콘은 디바이스를 의존해야합니다.

리모콘이 디바이스를 의존하는 것을 bridge(다리) 로 표현한 것이 바로 브릿지패턴입니다.

 

 

 

Remote

public abstract class Remote {

    protected final Device device;

    public Remote(Device device) {
        this.device = device;
    }

    public abstract void power();

}

 

public class BasicRemote extends Remote {

    public BasicRemote(Device device) {
        super(device);
    }

    @Override
    public void power() {
        if (device.isOn()) {
            device.off();
        } else {
            device.on();
        }
    }
}

public class SmartRemote extends Remote {

    public SmartRemote(Device device) {
        super(device);
    }

    @Override
    public void power() {
        if (device.isOn()) {
            device.off();
        } else {
            device.on();
        }
    }

    public void runApp(String app) {
        device.runApp(app);
    }
}

 

 

Device

public interface Device {

    void on();
    void off();
    boolean isOn();
    void runApp(String app);

}

 

public class TV implements Device {

    private boolean on = false;

    @Override
    public void on() {
        on = true;
        System.out.println("TV is on");
    }

    @Override
    public void off() {
        on = false;
        System.out.println("TV is off");
    }

    @Override
    public boolean isOn() {
        return on;
    }

    @Override
    public void runApp(String app) {
        if (isOn()) {
            System.out.println("TV Run App " + app);
        } else {
            System.out.println("TV is not On");
        }
    }
}

public class Radio implements Device {

    private boolean on = false;
    @Override
    public void on() {
        on = true;
        System.out.println("Radio is on");
    }

    @Override
    public void off() {
        on = false;
        System.out.println("Radio is off");
    }

    @Override
    public boolean isOn() {
        return on;
    }

    @Override
    public void runApp(String app) {

    }

}

public class Computer implements Device {

    private boolean on = false;

    @Override
    public void on() {
        on = true;
        System.out.println("Computer is on");
    }

    @Override
    public void off() {
        on = false;
        System.out.println("Computer is off");
    }

    @Override
    public boolean isOn() {
        return on;
    }

    @Override
    public void runApp(String app) {
        if (isOn()) {
            System.out.println("Computer run app : " + app);
        } else {
            System.out.println("Computer is not On");
        }
    }
}

 

 

실행결과

    public static void main(String[] args) {
        Device radio = new Radio();
        Remote basicRemote = new BasicRemote(radio);
        basicRemote.power();

        Device tv = new TV();
        SmartRemote smartRemote = new SmartRemote(tv);
        smartRemote.power();
        smartRemote.runApp("YouTube");

        smartRemote.power();
        smartRemote.runApp("Netflix");

    }
Radio is on
TV is on
TV Run App YouTube
TV is off
TV is not On

 

 

브릿지 패턴은 추상클래스와 구현클래스를 분리할 수 있어서 독립적인 확장이 가능합니다. 그리고 런타임시점에 구현체를 변경할 수 있다는 장점이 있습니다. 다만 이 브릿지패턴도 객체지향언어가 모두 그렇듯 분리하면 할수록 코드 복잡성이 증가하는 단점이 있습니다.

어댑터 패턴

어댑터 패턴은 호환되지 않은 인터페이스를 연결하는 디자인패턴입니다. 어댑터 패턴은 어려운거 없으니 바로 확인해보겠습니다.

 

 

 

 

 

public interface Target {

    void doSomething();
}
public class Adaptee {

    public void action() {
        System.out.println("action");
    }
}

 

Target과 Adaptee 는 서로 호환될 수 없는 관계입니다. 이 관계를 호환되게 만드는 것이 어댑터 패턴입니다.

 

 

우리는 기존코드를 수정하지 않으면서 Target 을 통해 Adaptee 클래스를 통제하고싶습니다. 그래서 두 클래스 사이에 새로운 클래스를 추가하겠습니다.

 

public class Adapter implements Target {

    private final Adaptee adaptee;

    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void doSomething() {
        adaptee.action();
    }
}

 

어댑터 패턴 끝났습니다.

 

 

 

Target 인터페이스를 Adapter 클래스에서 상속받고 필드에 Adaptee 를 받아 doSomething 에서 action 메소드를 오버라이딩 했습니다.

 

 

 

이 어댑터 패턴은 기존 코드의 수정 없이 호환되지 않는 클래스를 이어줄 수 있다는 장점이 있습니다. 다만 호환되지 않는 클래스의 연결로 인해 코드 복잡성이 증가하면서 유지보수가 어려워 질 수 있다는 단점도 존재합니다.

+ Recent posts