디자인 패턴 이해하기

Susan Sarandon
풀어 주다: 2024-11-03 10:54:02
원래의
313명이 탐색했습니다.

이제 AI 시대가 도래했습니다. 지금은 const fetch = require('node-fetch') ? (현재 ChatGPT와 Gemini 모두에 해당) 인터넷과 그 콘텐츠인 순환 기계의 또 다른 회전을 제공합니다.

콘텐츠의 융합 속에서 디자인 패턴이 다시 등장하고 있습니다

Demystifying Design Patterns

Node(???)에서 디자인 패턴을 적용하는 방법을 설명하는 게시물부터 Java(2014년 3월에 출시된 Java 8에서 람다 추가)에서 팩토리 패턴을 적용하는 방법과 같은 구식 내용을 자세히 설명하는 게시물까지 ).

정의

리팩토링 전문가를 우연히 만난 적이 있나요?
컴퓨터 과학, 특히 프로그래밍을 배우는 여정에서 아마도 방문했을 웹사이트일 것입니다. 디자인 패턴 섹션은 매우 잘 설명되어 있으며 수년 동안 다양한 포럼을 통해 가장 많이 공유된 섹션 중 하나입니다.

디자인 패턴의 정의를 살펴보면 다음과 같습니다.

디자인 패턴은 일반적인 문제에 대한 일반적인 솔루션입니다
소프트웨어 디자인에서. 각 패턴은 청사진과 같습니다
특정 문제를 해결하기 위해 사용자 정의할 수 있습니다
코드에 디자인 문제가 있습니다.

그렇다면 왜 이 게시물을 작성하는 걸까요? 즉, 위에 링크된 웹사이트에는 많은 정보가 있습니다. 이게 전부일 수도 있어요.

문제는 저는 항상 이 정의를 받아들이는 데 어려움을 겪었습니다... "내 코드에서 특정 디자인 문제를 해결하기 위해"... 내 코드에서? 내 코드에 해결해야 할 문제가 있나요?

정의, 재해석

실제로는 프로젝트에 사용된 프로그래밍 언어에 추상화가 부족한특정 "무언가"를 코딩해야 하는 경우가 발생합니다.

간단하고 간단합니다. 아직 마음에 들지 않는 경우를 대비해 코드를 포함한 몇 가지 예를 살펴보겠습니다.

이것은 Java(주로 객체 지향 프로그래밍 언어)에서 팩토리 패턴을 매우 간단하게 구현한 것입니다.

public class ShapeFactory {
  public Shape createShape(String type) {
    if (type.equalsIgnoreCase("CIRCLE")) {
      return new Circle();
    } else if (type.equalsIgnoreCase("SQUARE")) {
      return new Square();
    } 
    return null;   
  }
}
로그인 후 복사
로그인 후 복사

그런 다음 Java 8 (2014년 3월, 잊어버린 경우를 대비해)에는 Lambdas(함수형 프로그래밍의 개념)가 추가되어 대신 이 작업을 수행할 수 있습니다.

Map<String, Supplier<Shape>> shapeFactory = new HashMap<>();
shapeFactory.put("CIRCLE", Circle::new);
shapeFactory.put("SQUARE", Square::new);

Shape circle = shapeFactory.get("CIRCLE").get();
로그인 후 복사
로그인 후 복사

다시는 공장 설계 패턴이 필요하지 않습니다(적어도 Java에서는).

예, 공장 패턴이 대부분의 사람들이 항상 사용하는 예라는 것을 알고 있습니다. 하지만 다른 패턴은 어떻게 되나요? 그러면 다른 프로그래밍 언어에서는 어떻게 되나요?

Typescript의 방문자 패턴은 다음과 같습니다.

interface Shape {
  draw(): void;
  accept(visitor: ShapeVisitor): void; 
}

class Circle implements Shape {
  radius: number;

  constructor(radius: number) {
    this.radius = radius;   

  }

  draw() {
    console.log("Drawing a circle");
  }

  accept(visitor: ShapeVisitor) {
    visitor.visitCircle(this); 
  }
}

class Square implements Shape {
  sideLength: number;

  constructor(sideLength: number) {
    this.sideLength = sideLength;
  }

  draw() {
    console.log("Drawing a square");
  }

  accept(visitor: ShapeVisitor) {
    visitor.visitSquare(this);
  }
}

interface ShapeVisitor {
  visitCircle(circle: Circle): void;
  visitSquare(square: Square): void;
}

class AreaCalculator implements ShapeVisitor {
  private area = 0;

  visitCircle(circle: Circle) { 
    this.area = Math.PI * circle.radius * circle.radius;
    console.log(`Circle area: ${this.area}`);
  }

  visitSquare(square: Square) {
    this.area = square.sideLength * square.sideLength;
    console.log(`Square area: ${this.area}`);
  }

  getArea(): number {
    return this.area;
  }
}

// Using the Visitor
const circle = new Circle(5);
const square = new Square(4);
const calculator = new AreaCalculator();

circle.accept(calculator); 
square.accept(calculator); 
로그인 후 복사
로그인 후 복사

다음 코드는 방문자 패턴 대신 리플렉션(런타임에 자체 객체를 검사하고 조작하는 언어의 기능)을 사용한다는 점만 제외하면 완전히 동일한 작업을 수행합니다.

interface Shape {
  draw(): void;
}

class Circle implements Shape { 
  // ... (same as before)
  radius: number;
}

class Square implements Shape {
  // ... (same as before)
  sideLength: number;
}

function calculateArea(shape: Shape) {
  if (shape instanceof Circle) {
    const circle = shape as Circle; // Type assertion
    const area = Math.PI * circle.radius * circle.radius;
    console.log(`Circle area: ${area}`);
  } else if (shape instanceof Square) {
    const square = shape as Square; // Type assertion
    const area = square.sideLength * square.sideLength;
    console.log(`Square area: ${area}`);
  }
}

const circle = new Circle(5);
const square = new Square(4);

calculateArea(circle);
calculateArea(square);
로그인 후 복사
로그인 후 복사

이제 TypeScript에서도 관찰자 패턴을 사용할 수 있습니다.

interface Observer {
  update(data: any): void;
}

class NewsPublisher {
  private observers: Observer[] = [];

  subscribe(observer: Observer) {
    this.observers.push(observer);
  }

  unsubscribe(observer: Observer) {
    this.observers = this.observers.filter(o => o !== observer);
  }

  notify(news:   
 string) {
    this.observers.forEach(observer => observer.update(news));
  }
}

class NewsletterSubscriber implements Observer {
  update(news: string) {
    console.log(`Received news: ${news}`);
  }
}

// Using the Observer
const publisher = new NewsPublisher();
const subscriber1 = new NewsletterSubscriber();
const subscriber2 = new NewsletterSubscriber();

publisher.subscribe(subscriber1);
publisher.subscribe(subscriber2);

publisher.notify("New product launched!");
로그인 후 복사
로그인 후 복사

동일하지만 내장된(Node API에) EventEmitter를 사용합니다:

public class ShapeFactory {
  public Shape createShape(String type) {
    if (type.equalsIgnoreCase("CIRCLE")) {
      return new Circle();
    } else if (type.equalsIgnoreCase("SQUARE")) {
      return new Square();
    } 
    return null;   
  }
}
로그인 후 복사
로그인 후 복사

그 시점에서 "문제"가 OOP 구현이라는 것을 깨달았을 수도 있으며, 당신의 말이 완전히 옳기는 하지만 완전히는 아닙니다.

모든 프로그래밍 패러다임은 특히 가장 순수한 형태로 받아들여지면 고유한 특징, 어려움 또는 "직선으로는 달성할 수 없는 것"이 ​​있습니다.

함수형 프로그래밍 영역으로 들어가 보겠습니다. 아마도 모나드(Monads)에 대해 들어보셨을 것입니다.

당신이 수학적 정의의 함정에 빠졌든 아니든, 우리 소프트웨어 개발자들은 모나드를 디자인 패턴으로도 이해할 수 있습니다. 예상치 못한 일이 일어나지 않는 순수 기능의 세계에서는 부작용을 상상하기 어려운데, 대부분의 소프트웨어 제품에는 부작용이 필요한데 어떻게 해야 할까요...?

이것은 하스켈의 IO 모나드의 예입니다:

Map<String, Supplier<Shape>> shapeFactory = new HashMap<>();
shapeFactory.put("CIRCLE", Circle::new);
shapeFactory.put("SQUARE", Square::new);

Shape circle = shapeFactory.get("CIRCLE").get();
로그인 후 복사
로그인 후 복사

부작용(파일 읽기)은 IO 모나드에 포함되어 있습니다.

typescript를 사용하여 모나딕 예제를 추가해 보겠습니다.

interface Shape {
  draw(): void;
  accept(visitor: ShapeVisitor): void; 
}

class Circle implements Shape {
  radius: number;

  constructor(radius: number) {
    this.radius = radius;   

  }

  draw() {
    console.log("Drawing a circle");
  }

  accept(visitor: ShapeVisitor) {
    visitor.visitCircle(this); 
  }
}

class Square implements Shape {
  sideLength: number;

  constructor(sideLength: number) {
    this.sideLength = sideLength;
  }

  draw() {
    console.log("Drawing a square");
  }

  accept(visitor: ShapeVisitor) {
    visitor.visitSquare(this);
  }
}

interface ShapeVisitor {
  visitCircle(circle: Circle): void;
  visitSquare(square: Square): void;
}

class AreaCalculator implements ShapeVisitor {
  private area = 0;

  visitCircle(circle: Circle) { 
    this.area = Math.PI * circle.radius * circle.radius;
    console.log(`Circle area: ${this.area}`);
  }

  visitSquare(square: Square) {
    this.area = square.sideLength * square.sideLength;
    console.log(`Square area: ${this.area}`);
  }

  getArea(): number {
    return this.area;
  }
}

// Using the Visitor
const circle = new Circle(5);
const square = new Square(4);
const calculator = new AreaCalculator();

circle.accept(calculator); 
square.accept(calculator); 
로그인 후 복사
로그인 후 복사

고전적인 이야기로, 아마도 모나드는 인터넷에서 50번 정도 본 것 같은데, 그게 정말 뭔데요?

문제 해결하려는 문제:

interface Shape {
  draw(): void;
}

class Circle implements Shape { 
  // ... (same as before)
  radius: number;
}

class Square implements Shape {
  // ... (same as before)
  sideLength: number;
}

function calculateArea(shape: Shape) {
  if (shape instanceof Circle) {
    const circle = shape as Circle; // Type assertion
    const area = Math.PI * circle.radius * circle.radius;
    console.log(`Circle area: ${area}`);
  } else if (shape instanceof Square) {
    const square = shape as Square; // Type assertion
    const area = square.sideLength * square.sideLength;
    console.log(`Square area: ${area}`);
  }
}

const circle = new Circle(5);
const square = new Square(4);

calculateArea(circle);
calculateArea(square);
로그인 후 복사
로그인 후 복사

객체의 속성을 정의하는 것을 잊었습니다! ?

실제 사용 사례에서는 주로 데이터베이스나 파일에서 읽는 것과 같은 부작용으로 인한 입력이 됩니다

이제 그렇게 하면:

interface Observer {
  update(data: any): void;
}

class NewsPublisher {
  private observers: Observer[] = [];

  subscribe(observer: Observer) {
    this.observers.push(observer);
  }

  unsubscribe(observer: Observer) {
    this.observers = this.observers.filter(o => o !== observer);
  }

  notify(news:   
 string) {
    this.observers.forEach(observer => observer.update(news));
  }
}

class NewsletterSubscriber implements Observer {
  update(news: string) {
    console.log(`Received news: ${news}`);
  }
}

// Using the Observer
const publisher = new NewsPublisher();
const subscriber1 = new NewsletterSubscriber();
const subscriber2 = new NewsletterSubscriber();

publisher.subscribe(subscriber1);
publisher.subscribe(subscriber2);

publisher.notify("New product launched!");
로그인 후 복사
로그인 후 복사

프로그램이 폭발합니다.

Maybe 모나드가 없는 솔루션:

import { EventEmitter } from 'events';

class NewsPublisher extends EventEmitter {
  publish(news: string) {
    this.emit('news', news);
  }
}

const publisher = new NewsPublisher();

publisher.on('news', (news) => {
  console.log(`All subscribers received the news: ${news}`);
});

publisher.publish("New product launched!");
로그인 후 복사

프로그램이 터지지 않습니다.

maybe 모나드는 선택적 연결 연산자로 인해 JavaScript나 typescript에서는 필요하지 않지만 이를 구현하지 않는 언어를 사용하는 경우... 음, 어쩌면 모나드를 적용할 수 있습니다. 디자인 패턴이라고 할까요?

네 알아요. Maybe라는 개념을 막 배워서 그것을 6개의 사이드 프로젝트에 한꺼번에 열심히 적용한 사람들이 있는데, 지금은 파티에서 "필요하지 않다"고 말하면서 낄낄거리고 있습니다. 그래도 계속 사용할 수 있습니다. 사실 괜찮다고 느끼면 그렇게 하도록 초대합니다 (결국 원하는 것은 무엇이든 할 수 있는 예쁜 얼굴의 코드입니다! ?)


하지만 다시 기본으로 돌아가세요. 다른 패러다임은 어떻습니까? OOP/FP 상자 밖에서 생각하고 있다면 마음에 듭니다!

공식적으로 '디자인 패턴'이라고 부르지는 않더라도 모든 패러다임에는 확실히 고유한 반복 솔루션과 기술이 있습니다.

다음은 몇 가지 예입니다(생각하지 않게 해준 Gemini에게 감사하고, 예쁜 형식과 부가 가치를 알려주셔서 감사합니다 ?):

논리 프로그래밍:
  • 제약 논리 프로그래밍: 이 패러다임에는 제약 조건과 변수 간의 관계를 정의한 다음 시스템이 이러한 제약 조건을 충족하는 솔루션을 찾도록 하는 작업이 포함됩니다. 역추적제약조건 전파와 같은 기술은 이 패러다임에서 효율적인 문제 해결에 매우 중요합니다. (AI를 다룰 때 꽤 유용합니다).
  • 연역적 데이터베이스: 이 데이터베이스는 논리적 규칙과 추론을 사용하여 기존 데이터에서 새로운 정보를 도출합니다. 정방향/역방향 연결과 같은 기술은 이러한 데이터베이스가 작동하는 방식의 기본이며 이 패러다임 내에서 패턴으로 간주될 수 있습니다.
동시 프로그래밍:
  • 메시지 전달: 여러 프로세스가 동시에 실행되는 동시 시스템에서 메시지 전달은 통신 및 조정을 위한 일반적인 기술입니다. 생산자-소비자리더-라이터와 같은 패턴은 리소스에 대한 동시 액세스를 관리하고 데이터 일관성을 보장하기 위한 확립된 솔루션을 제공합니다.
  • 동기화 기본 요소: 공유 리소스에 대한 액세스를 제어하는 ​​데 사용되는 뮤텍스, 세마포어조건 변수와 같은 하위 수준 구성입니다. 동시 프로그램에서. 전통적인 의미의 "패턴"은 아니지만 일반적인 동시성 문제에 대한 잘 정의된 솔루션을 나타냅니다.

데이터 지향 프로그래밍:

  • 데이터 변환 파이프라인: 이 패러다임은 일련의 작업을 통한 데이터 변환을 강조합니다. map, filterreduce와 같은 기술(함수형 프로그래밍에서도 일반적이며 추가 이후 javascript에서 많이 사용됨)은 구성을 위한 기본 구성 요소입니다. 이러한 파이프라인은 이 패러다임 내에서 패턴으로 간주될 수 있습니다.
  • ECS(Entity-Component-System): 이 아키텍처 패턴은 게임 개발 및 기타 데이터 집약적 애플리케이션에서 널리 사용됩니다. 여기에는 엔터티를 구성 요소(데이터)와 시스템(논리)으로 분해하여 데이터 지역성과 효율적인 처리를 촉진하는 작업이 포함됩니다.

"기술"과 "패턴"이 많이 있습니다. 이 목록은 궁금하신 분들을 위해 참고할 내용을 제공하기 위한 것입니다.

이 내용이 도움이 되기를 바랍니다. 곧 읽어 보시기 바랍니다!

Demystifying Design Patterns


? 급하신 분들을 위해 요약!

'디자인 패턴'이라는 용어는 OOP와 가장 밀접하게 연관되어 있지만 다른 패러다임에는 고유한 반복 솔루션 및 기술 세트가 있습니다. 이러한 기술은 해당 패러다임의 특정 과제와 제약을 해결하여 일반적인 문제에 대한 확립된 접근 방식을 제공합니다. 따라서 공식적으로 항상 "디자인 패턴"으로 표시되지는 않더라도 개발자를 효과적이고 유지 관리 가능한 솔루션으로 안내하는 비슷한 목적을 제공합니다.

디자인 패턴은 우리가 사용하는 프로그래밍 언어에 추상화가 부족한 기능을 패치하기 위한 잘 알려진 해결 방법으로 이해할 수 있습니다.

이 게시물은 거의 전적으로 제가 작성했으며, 예시는 Gemini 1.5 Pro를 사용하여 작성되었습니다

위 내용은 디자인 패턴 이해하기의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:dev.to
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
저자별 최신 기사
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿