티스토리 뷰

728x90

어댑터 패턴

설명:
어댑터 패턴은 호환되지 않는 인터페이스를 연결하여 기존 클래스를 새 시스템에서 사용할 수 있게 합니다. 예를 들어, 레거시 시스템의 오래된 API를 최신 시스템과 통합할 때 유용합니다. 이는 기존 코드를 수정하지 않고 새로운 인터페이스를 제공하여 호환성을 확보합니다.
사용처:
  • 레거시 시스템과 새 코드 통합, 예: 오래된 API와 최신 시스템 연결.
자바 예제:
// 기존 클래스 (Adaptee)
class OldPrinter {
    public void printMessage(String msg) {
        System.out.println("Old Printer: " + msg);
    }
}

// 새 인터페이스 (Target)
interface TextDisplay {
    void displayText(String text);
}

// 어댑터 (Adapter)
class PrinterAdapter implements TextDisplay {
    private OldPrinter printer;

    public PrinterAdapter(OldPrinter printer) {
        this.printer = printer;
    }

    @Override
    public void displayText(String text) {
        printer.printMessage(text);
    }
}

// 사용 예
public class Main {
    public static void main(String[] args) {
        OldPrinter oldPrinter = new OldPrinter();
        TextDisplay adapter = new PrinterAdapter(oldPrinter);
        adapter.displayText("Hello, World!");  // 출력: Old Printer: Hello, World!
    }
}

브리지 패턴

설명:
브리지 패턴은 추상화와 구현을 분리하여 둘을 독립적으로 변형할 수 있게 합니다. 예를 들어, GUI 시스템에서 버튼을 렌더링하는 방식(예: SVG, Canvas)을 변경하더라도 버튼 클래스는 영향을 받지 않습니다. 이는 플랫폼 독립성을 높이는 데 유용합니다.
사용처:
  • 디바이스 드라이버, 예: 프린터 인터페이스와 하드웨어 구현 분리.
자바 예제:
// 추상화 (Abstraction)
abstract class Shape {
    protected Renderer renderer;

    public Shape(Renderer renderer) {
        this.renderer = renderer;
    }

    public abstract void draw();
}

// 구체적인 추상화 (Concrete Abstraction)
class Circle extends Shape {
    public Circle(Renderer renderer) {
        super(renderer);
    }

    @Override
    public void draw() {
        renderer.drawCircle();
    }
}

// 구현 인터페이스 (Implementor)
interface Renderer {
    void drawCircle();
}

// 구체적인 구현 (Concrete Implementor)
class SVGRenderer implements Renderer {
    @Override
    public void drawCircle() {
        System.out.println("Drawing circle in SVG");
    }
}

class CanvasRenderer implements Renderer {
    @Override
    public void drawCircle() {
        System.out.println("Drawing circle in Canvas");
    }
}

// 사용 예
public class Main {
    public static void main(String[] args) {
        Shape circle = new Circle(new SVGRenderer());
        circle.draw();  // 출력: Drawing circle in SVG

        circle = new Circle(new CanvasRenderer());
        circle.draw();  // 출력: Drawing circle in Canvas
    }
}

컴포지트 패턴

설명:
컴포지트 패턴은 부분-전체 계층 구조를 표현하여 개별 객체와 객체 집합을 동일하게 다룰 수 있게 합니다. 예를 들어, 파일 시스템에서 디렉토리(디렉토리와 파일 포함)와 파일을 동일한 방식으로 처리할 수 있습니다. 이는 계층적 구조를 쉽게 관리하게 합니다.
사용처:
  • 파일 시스템, 예: 디렉토리와 파일의 계층적 구조.
자바 예제:
import java.util.ArrayList;
import java.util.List;

// 컴포넌트 인터페이스 (Component)
interface FileSystemItem {
    void print();
}

// 리프 노드 (Leaf)
class File implements FileSystemItem {
    private String name;

    public File(String name) {
        this.name = name;
    }

    @Override
    public void print() {
        System.out.println("File: " + name);
    }
}

// 컴포지트 노드 (Composite)
class Directory implements FileSystemItem {
    private String name;
    private List<FileSystemItem> items;

    public Directory(String name) {
        this.name = name;
        this.items = new ArrayList<>();
    }

    public void addItem(FileSystemItem item) {
        items.add(item);
    }

    @Override
    public void print() {
        System.out.println("Directory: " + name);
        for (FileSystemItem item : items) {
            item.print();
        }
    }
}

// 사용 예
public class Main {
    public static void main(String[] args) {
        File file1 = new File("file1.txt");
        File file2 = new File("file2.txt");

        Directory subDir = new Directory("subdir");
        subDir.addItem(file2);

        Directory root = new Directory("root");
        root.addItem(file1);
        root.addItem(subDir);

        root.print();
        // 출력:
        // Directory: root
        // File: file1.txt
        // Directory: subdir
        // File: file2.txt
    }
}

데코레이터 패턴

설명:
데코레이터 패턴은 객체에 동적으로 새로운 책임을 추가할 수 있게 합니다. 기존 객체를 감싸는 데코레이터를 통해 기능을 확장하며, 예를 들어 데이터 스트림에 암호화나 압축을 추가할 수 있습니다. 이는 기존 클래스를 수정하지 않고 기능을 확장할 수 있게 합니다.
사용처:
  • 데이터 스트림에 암호화 또는 압축 추가, 예: 네트워크 데이터 보호.
자바 예제:
// 컴포넌트 인터페이스 (Component)
interface Stream {
    void write(String data);
}

// 구체적인 컴포넌트 (Concrete Component)
class BasicStream implements Stream {
    @Override
    public void write(String data) {
        System.out.println("Writing: " + data);
    }
}

// 데코레이터 추상 클래스 (Decorator)
abstract class StreamDecorator implements Stream {
    protected Stream stream;

    public StreamDecorator(Stream stream) {
        this.stream = stream;
    }

    @Override
    public void write(String data) {
        stream.write(data);
    }
}

// 구체적인 데코레이터 (Concrete Decorator)
class EncryptedStream extends StreamDecorator {
    public EncryptedStream(Stream stream) {
        super(stream);
    }

    @Override
    public void write(String data) {
        // 암호화 시뮬레이션
        String encrypted = "Encrypted_" + data;
        super.write(encrypted);
    }
}

// 사용 예
public class Main {
    public static void main(String[] args) {
        Stream stream = new BasicStream();
        Stream encryptedStream = new EncryptedStream(stream);
        encryptedStream.write("Hello, World!");  // 출력: Writing: Encrypted_Hello, World!
    }
}

파사드 패턴

설명:
파사드 패턴은 복잡한 서브시스템에 통합된 인터페이스를 제공하여 클라이언트가 쉽게 사용할 수 있게 합니다. 예를 들어, 데이터베이스 연결 풀을 관리하는 복잡한 과정을 단순화하여 클라이언트가 쿼리만 실행하면 됩니다. 이는 시스템의 복잡성을 숨기고 사용성을 높입니다.
사용처:
  • 데이터베이스 연결 풀, 예: 연결 관리 세부 사항 숨김.
자바 예제:
// 서브시스템 클래스
class DatabaseConnection {
    public static Connection getConnection() {
        // 연결 가져오기 시뮬레이션
        return new Connection();
    }
}

class Connection {
    // 연결 메서드 시뮬레이션
    public void executeQuery(String query) {
        System.out.println("Executing query: " + query);
    }
}

// 파사드 클래스
class DatabaseFacade {
    private Connection connection;

    public DatabaseFacade() {
        connection = DatabaseConnection.getConnection();
    }

    public void performQuery(String query) {
        connection.executeQuery(query);
    }
}

// 사용 예
public class Main {
    public static void main(String[] args) {
        DatabaseFacade facade = new DatabaseFacade();
        facade.performQuery("SELECT * FROM table");  // 출력: Executing query: SELECT * FROM table
    }
}

플라이웨이트 패턴

설명:
플라이웨이트 패턴은 메모리 효율성을 높이기 위해 객체를 공유하여 동일한 데이터를 여러 객체가 재사용하게 합니다. 예를 들어, 텍스트 편집기에서 여러 문자가 동일한 폰트와 스타일을 공유할 수 있습니다. 이는 메모리 사용량을 줄이는 데 유용합니다.
사용처:
  • 텍스트 편집기, 예: 문자열 객체 공유.
자바 예제:
import java.util.HashMap;
import java.util.Map;

// 플라이웨이트 인터페이스
interface Character {
    void display(char c);
}

// 구체적인 플라이웨이트
class CharacterImpl implements Character {
    private String font;
    private String style;

    public CharacterImpl(String font, String style) {
        this.font = font;
        this.style = style;
    }

    @Override
    public void display(char c) {
        System.out.println("Displaying '" + c + "' with font " + font + " and style " + style);
    }
}

// 플라이웨이트 팩토리
class CharacterFactory {
    private Map<String, Character> characters = new HashMap<>();

    public Character getCharacter(String font, String style) {
        String key = font.trim() + "_" + style;
        if (!characters.containsKey(key)) {
            characters.put(key, new CharacterImpl(font, style));
        }
        return characters.get(key);
    }
}

// 사용 예
public class Main {
    public static void main(String[] args) {
        CharacterFactory factory = new CharacterFactory();
        Character charA = factory.getCharacter("Arial", "bold");
        charA.display('A');  // 출력: Displaying 'A' with font Arial and style bold

        Character charB = factory.getCharacter("Arial", "bold");
        charB.display('B');  // 출력: Displaying 'B' with font Arial and style bold

        // charA와 charB는 동일한 플라이웨이트 객체를 공유
    }
}

프록시 패턴

설명:
프록시 패턴은 다른 객체에 대한 대리자나 플레이스홀더를 제공하여 객체 접근을 제어합니다. 예를 들어, 이미지를 로드하는 데 시간이 오래 걸리면 프록시를 통해 이미지를 필요할 때 로드할 수 있습니다. 이는 보안 체크, 지연 로드, 원격 객체 접근 등에 유용합니다.
사용처:
  • 네트워크를 통한 원격 객체 접근, 예: 보안 체크를 위한 보호 프록시.
자바 예제:
// 주제 인터페이스 (Subject)
interface Displayable {
    void display();
}

// 실제 주제 (Real Subject)
class Image implements Displayable {
    private String filename;

    public Image(String filename) {
        this.filename = filename;
        loadFromDisk();
    }

    private void loadFromDisk() {
        System.out.println("Loading " + filename);
    }

    @Override
    public void display() {
        System.out.println("Displaying " + filename);
    }
}

// 프록시 (Proxy)
class ImageProxy implements Displayable {
    private String filename;
    private Image realImage;

    public ImageProxy(String filename) {
        this.filename = filename;
    }

    @Override
    public void display() {
        if (realImage == null) {
            realImage = new Image(filename);
        }
        realImage.display();
    }
}

// 사용 예
public class Main {
    public static void main(String[] args) {
        Displayable proxy = new ImageProxy("image.jpg");
        proxy.display();  // 출력: Loading image.jpg, Displaying image.jpg
        proxy.display();  // 출력: Displaying image.jpg (재로딩 없음)
    }
}

추가 고려사항

이러한 패턴들은 단순히 코드 작성 도구가 아니라, 개발자 간 커뮤니케이션을 용이하게 하는 공통 언어를 제공합니다. 이는 팀 협업을 강화하고, 코드의 가독성과 유지보수성을 높이는 데 기여합니다. 또한, 패턴은 시간이 지남에 따라 개선되며, 새로운 패턴(예: 동시성 패턴)도 등장하고 있습니다. 그러나 모든 상황에 적합하지 않을 수 있으며, 과도한 사용은 코드 복잡성을 증가시킬 수 있습니다.
728x90