ホームページ > ウェブフロントエンド > jsチュートリアル > デザインパターンの謎を解く

デザインパターンの謎を解く

Susan Sarandon
リリース: 2024-11-03 10:54:02
オリジナル
344 人が閲覧しました

AI 時代が到来し、現時点では const fetch = require('node-fetch') で Node コードを吐き出すという大きな進歩です。 (今日の時点では ChatGPT と Gemini の両方に当てはまります) そして、インターネットとそのコンテンツであるサイクリック マシンのさらに別のスピンを供給します。

コンテンツの融合の中で、デザインパターンが再び現れています

Demystifying Design Patterns

Node(???) でデザイン パターンを適用する方法を説明する投稿から、Java でファクトリー パターンを適用する方法など、古いものを詳細に説明する投稿まで (2014 年 3 月にリリースされた Java 8 に Lambda が追加されました) ).

意味

リファクタリングの第一人者に出会ったことはありますか?
これは、コンピューター サイエンス、特にプログラミングを学習する過程でおそらくアクセスした Web サイトです。このデザイン パターンのセクションは非常に詳しく説明されており、長年にわたってさまざまなフォーラムを通じて最も共有されているセクションの 1 つです。

デザイン パターンとは何かを定義すると、次のことがわかります。

デザイン パターンは、一般的な問題に対する典型的な解決策です
ソフトウェア設計で。それぞれの模様はまるで設計図のようです
特定の問題を解決するためにカスタマイズできる
コード内の設計上の問題。

では、なぜこの投稿をするのでしょうか?つまり、上にリンクした Web サイトにはたくさんの情報があります。これですべてかもしれません。

実際のところ、私はこの定義を受け入れるのにいつも苦労していました...「コード内の特定の設計上の問題を解決するため」...私のコードで?私のコードには解決する必要がある問題がありますか?

定義、再考

実際に何が起こるかというと、プロジェクトで使用されているプログラミング言語には抽象化が欠けている特定の「何か」をコーディングする必要があるということです。

単純明快。まだピンとこない方のために、コード付きの例をいくつか見てみましょう。

これは、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); 
ログイン後にコピー
ログイン後にコピー
次のコードはまったく同じことを行っていますが、Visitor パターンの代わりに

リフレクション (実行時に独自のオブジェクトを調べて操作する言語の機能) を使用しています。

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 実装にあることに気づいたかもしれません。それは非常に正しいですが、完全ではありません。

すべてのプログラミング パラダイムは、特に最も純粋な形で解釈した場合、その癖、困難、または言うなれば「直線的には達成できないもの」を持っています。

関数型プログラミングの領域に入ってみましょう。おそらくモナドについて聞いたことがあるでしょう。

あなたが数学的定義のマインドトラップに陥ったかどうかに関係なく、私たちソフトウェア開発者はモナドをデザインパターンとして理解することもできます。これは、予期せぬことが何も起こらない純粋関数の世界では副作用を考えるのが難しいためですが、ほとんどのソフトウェア製品には副作用が必要なので、どうすれば...?

これは Haskell の 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 モナドまたは meybe モナドを適用できます。デザインパターンとでも言いましょうか

はい、知っています、Maybe のことを学んだばかりでそれを一度に 6 つのサイドプロジェクトに熱心に適用した人がいるのに、今では私がパーティーで「それは必要ない」と言って笑いの種になっています。ただし、まだ使用することはできます。実際、これがクールだと感じたら、ぜひ使用してください。(結局のところ、それはあなたのコードであり、そのかわいい顔は何をしても構いません! ?)


しかし、基本に立ち返ってください。他のパラダイムについてはどうですか? OOP/FP の枠にとらわれずに考えているなら、それがいいと思います。

すべてのパラダイムには、必ずしも正式に「デザイン パターン」と呼ばれるわけではないとしても、必ず独自の反復的なソリューションとテクニックがあります。

ここにいくつかの例を示します (私が考えることを避けてくれた Gemini に感謝します。きれいな書式設定と付加価値を提供してくれてありがとう?):

ロジックプログラミング:
  • 制約ロジック プログラミング: このパラダイムには、制約と変数間の関係を定義し、システムにそれらの制約を満たすソリューションを見つけさせることが含まれます。 バックトラッキング制約の伝播などの手法は、このパラダイムで効率的に問題を解決するために重要です。 (AI を扱う場合に非常に役立ちます)。
  • 演繹データベース: これらのデータベースは、論理ルールと推論を使用して、既存のデータから新しい情報を導き出します。 前方/後方チェーンのような技術は、これらのデータベースの動作方法の基本であり、このパラダイム内のパターンと見なすことができます。
同時プログラミング:
  • メッセージ パッシング: 複数のプロセスが同時に実行される同時システムでは、メッセージ パッシングは通信と調整のための一般的な手法です。 プロデューサー-コンシューマーリーダー-ライター のようなパターンは、リソースへの同時アクセスを管理し、データの一貫性を確保するための確立されたソリューションを提供します。
  • 同期プリミティブ: これらは、共有リソースへのアクセスを制御するために使用される、ミューテックスセマフォ、および条件変数のような低レベルの構成要素です。同時プログラムで。従来の意味での「パターン」ではありませんが、一般的な同時実行性の課題に対する明確に定義されたソリューションを表します。

データ指向プログラミング:

  • データ変換パイプライン: このパラダイムは、一連の操作によるデータの変換に重点を置いています。 mapfilterreduce などのテクニック (関数型プログラミングでも一般的で、JavaScript の追加以来よく使用されています) は、これらのパイプラインは、このパラダイム内のパターンとみなされる可能性があります。
  • エンティティ コンポーネント システム (ECS): このアーキテクチャ パターンは、ゲーム開発やその他のデータ集約型アプリケーションで一般的です。これには、エンティティをコンポーネント (データ) とシステム (ロジック) に分割し、データの局所性と効率的な処理を促進することが含まれます。

多くの「テクニック」と「パターン」があります。このリストは、興味がある場合に参照するスレッドを提供するためのものです。

これがお役に立てば幸いです。すぐにお読みください!

Demystifying Design Patterns


?まとめ、お急ぎの方へ!

「デザイン パターン」という用語は OOP と最も密接に関連付けられていますが、他のパラダイムにも独自の反復的なソリューションとテクニックのセットがあります。これらの技術は、それらのパラダイムの特定の課題と制約に対処し、一般的な問題に対する確立されたアプローチを提供します。したがって、必ずしも正式に「デザイン パターン」と名付けられているわけではありませんが、開発者を効果的で保守可能なソリューションに導くという同様の目的を果たします。

デザイン パターンは、使用しているプログラミング言語に抽象化が欠けている機能にパッチを適用するためのよく知られた回避策として理解できます。

この投稿はほぼすべて私によって書かれており、例は Gemini 1.5 Pro によって指定されています

以上がデザインパターンの謎を解くの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート