디자인 패턴

[Design Pattern] 구조 패턴(Structural Patterns) 🏗️✨

xeunnie 2025. 1. 16. 01:00
728x90
반응형

구조 패턴(Structural Patterns) 톺아보기!  🏗️

“객체들이 서로를 어떻게 돕고 협력할까?” 이번 모험의 주제는 바로 구조 패턴(Structural Patterns)입니다. 객체들의 관계와 구조를 설계하여 더 유연하고 유지보수하기 쉬운 시스템을 만드는 것이 목표예요. 프론트엔드 개발에서 UI 구성 요소 간의 연결을 어떻게 관리할지 고민할 때 딱 맞는 이야기죠.

이를 통해 코드가 더 유연해지고, 재사용 가능하며, 유지보수도 쉬워지게 설계할 수 있어요!


🏗️ 구조 패턴: 객체들의 협력과 조화

구조 패턴은 객체와 클래스 간의 관계를 다루며, 이를 통해 시스템이 더 쉽게 확장되고 변경될 수 있도록 설계합니다. 단순히 객체를 나열하는 게 아니라, 이들이 어떻게 서로 협력하고 결합할지 정의하는 데 초점을 맞추죠.

 

결과적으로, 코드를 다음과 같이 개선할 수 있습니다:

  1. 유연성: 객체 간 결합도를 낮추어 각 요소를 독립적으로 변경하거나 확장하기 쉬워집니다.
  2. 재사용성: 이미 설계된 객체나 클래스를 다양한 상황에서 다시 사용할 수 있습니다.
  3. 명확한 구조: 코드의 역할과 책임이 명확해지며, 가독성이 높아집니다.

🌟 주요 구조 패턴들

1️⃣  어댑터 패턴(Adapter Pattern)

 

서로 다른 언어를 사용하는 객체들을 연결하라!

어댑터 패턴은 인터페이스가 맞지 않는 객체들 사이를 연결하는 변환기 역할을 합니다.

예를 들어, 새로 도입한 라이브러리가 기존 코드와 맞지 않을 때, 두 시스템 간의 간극을 메우는 어댑터를 통해 이 둘을 연결할 수 있죠.

 

🚀 코드 예제: 어댑터 패턴

class LegacyPrinter {
  print(text) {
    console.log(`Legacy Printer: ${text}`);
  }
}

class ModernPrinter {
  printText(text) {
    console.log(`Modern Printer: ${text}`);
  }
}

// 어댑터 클래스
class PrinterAdapter {
  constructor(modernPrinter) {
    this.modernPrinter = modernPrinter;
  }

  print(text) {
    this.modernPrinter.printText(text);
  }
}

// 사용 예시
const legacyPrinter = new LegacyPrinter();
const modernPrinter = new PrinterAdapter(new ModernPrinter());

legacyPrinter.print("Hello from Legacy!"); // Legacy Printer: Hello from Legacy!
modernPrinter.print("Hello from Modern!"); // Modern Printer: Hello from Modern!

 

📝 설명:

  • 기존 코드를 변경하지 않고도 새로운 기능이나 객체를 통합할 수 있게 만들어 줍니다.
  • 새로운 인터페이스와 기존 인터페이스 간의 불일치를 해소해주는 역할을 하죠.

🤔 언제 쓰나요?

  • 기존 코드새로운 코드를 연결할 때
  • API가 변경되었을 때 하위 호환성을 유지하기 위해

2️⃣ 데코레이터 패턴(Decorator Pattern)

 

 기존 기능에 살짝 더 얹어주자!

 

데코레이터 패턴은 기존 객체에 추가적인 기능을 동적으로 부여합니다. 주요 특징은 기존 코드를 수정하지 않고 기능을 확장할 수 있다는 점이에요.

 

🚀 코드 예제: 데코레이터 패턴

class Coffee {
  cost() {
    return 5;
  }

  description() {
    return "Simple Coffee";
  }
}

class MilkDecorator {
  constructor(coffee) {
    this.coffee = coffee;
  }

  cost() {
    return this.coffee.cost() + 2;
  }

  description() {
    return `${this.coffee.description()} with Milk`;
  }
}

// 사용 예시
const simpleCoffee = new Coffee();
const milkCoffee = new MilkDecorator(simpleCoffee);

console.log(milkCoffee.description()); // Simple Coffee with Milk
console.log(milkCoffee.cost()); // 7

 

📝 설명:

  • 기본 객체를 손대지 않고, 새로운 기능을 “추가로 덧붙이는” 방법입니다.
  • React의 고차 컴포넌트(HOC)와 매우 유사한 개념입니다.

🤔 언제 쓰나요?

  • React의 HOC처럼 컴포넌트에 새로운 기능을 추가하여 동작을 확장해야 할 때
  • 상태 관리 라이브러리와 함께 미들웨어를 사용할 때
  • 상속 대신 조합을 통해 코드 재사용성을 높이고 싶을 때

3️⃣  프록시 패턴(Proxy Pattern)

직접 처리하지 말고 대리인을 세워라!

 

프록시 패턴은 다른 객체에 대한 대리 역할을 수행합니다.

객체 접근을 제어하거나, 추가 로직(캐싱, 로깅 등)을 실행하고 싶을 때 유용합니다.

예를 들어, 데이터베이스에 직접 접근하기 전에 캐싱 레이어를 두어 데이터를 효율적으로 관리할 수 있죠.

 

🚀 코드 예제: 프록시 패턴

class RealImage {
  constructor(fileName) {
    this.fileName = fileName;
    this.loadImage();
  }

  loadImage() {
    console.log(`Loading image: ${this.fileName}`);
  }

  display() {
    console.log(`Displaying image: ${this.fileName}`);
  }
}

class ProxyImage {
  constructor(fileName) {
    this.fileName = fileName;
    this.realImage = null;
  }

  display() {
    if (!this.realImage) {
      this.realImage = new RealImage(this.fileName);
    }
    this.realImage.display();
  }
}

// 사용 예시
const image = new ProxyImage("photo.png");
image.display(); // Loading image: photo.png, Displaying image: photo.png
image.display(); // Displaying image: photo.png

 

📝 설명:

  • 실제 객체를 감싸는 역할을 하여 접근을 제어하거나 추가적인 작업(캐싱, 로깅 등)을 수행합니다.
  • 리소스가 큰 객체를 지연 로딩할 때 유용합니다.

🤔 언제 쓰나요?

  • 리소스가 큰 객체를 지연 로딩할 때
  • 캐싱, 권한 확인, 리소스 관리가 필요할 때
  • 네트워크 요청을 캐싱하거나 추가 검증을 추가하고 싶을 때, 이를 효율적으로 처리하고 싶을 때

4️⃣ 컴포지트 패턴(Composite Pattern)

 

트리 구조로 복잡한 구조를 간단히 관리하라!

 

컴포지트 패턴은 객체들을 트리 구조로 구성하여 개별 객체와 그룹 객체를 동일하게 다룰 수 있게 합니다.

각각의 객체와 그룹 객체를 동일하게 다룰 수 있도록 설계되어, 복잡한 구조를 단순화할 수 있죠.

 

🚀 코드 예제: 컴포지트 패턴

class File {
  constructor(name) {
    this.name = name;
  }

  display() {
    console.log(`File: ${this.name}`);
  }
}

class Folder {
  constructor(name) {
    this.name = name;
    this.children = [];
  }

  add(child) {
    this.children.push(child);
  }

  display() {
    console.log(`Folder: ${this.name}`);
    this.children.forEach((child) => child.display());
  }
}

// 사용 예시
const root = new Folder("Root");
const file1 = new File("File1.txt");
const file2 = new File("File2.txt");
const subFolder = new Folder("SubFolder");

root.add(file1);
root.add(file2);
subFolder.add(new File("File3.txt"));
root.add(subFolder);

root.display();
// Folder: Root
// File: File1.txt
// File: File2.txt
// Folder: SubFolder
// File: File3.txt

 

📝 설명:

  • React의 컴포넌트 구조처럼, 계층적 데이터효율적으로 관리할 수 있습니다.
  • 개별 객체와 그룹 객체를 동일한 인터페이스로 다룰 수 있어, 코드의 단순화가 가능하죠.

🤔 언제 쓰나요?

  • React의 컴포넌트 트리 구조처럼 계층적 데이터를 관리할 때
  • 파일 시스템, UI 컴포넌트 관리 등 계층적 구조가 필요한 상황에서

🎯 프론트엔드에서의 활용

  • 어댑터 패턴: 다양한 API를 하나의 일관된 형식으로 변환
  • 데코레이터 패턴: 컴포넌트 기능 확장(HOC)
  • 프록시 패턴: API 요청 캐싱, 인증 처리
  • 컴포지트 패턴: React 컴포넌트 트리 구조 관리

 

구조 패턴은 객체들이 어떻게 협력하고 관계를 맺을지에 초점을 맞춥니다. 이를 활용하면 유연하고 확장 가능한 코드를 작성할 수 있어요!

다음 시간에는 행동 패턴(Behavioral Patterns)으로 넘어가, 객체들이 어떻게 상호작용하며 문제를 해결할지를 이야기해볼게요.

 

🌷전설의 개발자가 되어봅시당!🌷

728x90
반응형