首頁 > web前端 > js教程 > 主體

揭秘設計模式

Susan Sarandon
發布: 2024-11-03 10:54:02
原創
313 人瀏覽過

所以人工智慧時代已經到來,這是一個巨大的飛躍,目前,用 const fetch = require('node-fetch') 吐出 Node 程式碼? (對於今天的 ChatGPT 和 Gemini 都是如此)並為循環機器的另一個旋轉提供動力,即互聯網及其內容。

在內容的融合中,設計模式再次出現

Demystifying Design Patterns

從解釋如何在Node(???) 中應用設計模式的帖子到解釋所有詳細過時內容的帖子,例如如何在Java 中應用工廠模式(Java 8 於2014 年3 月發布加了Lambda) ).

定義

曾經偶然遇過重構大師嗎?
這是您在電腦科學(特別是程式設計)學習之旅中可能造訪過的網站。它的設計模式部分得到了很好的解釋,也是多年來透過不同論壇分享最多的部分之一。

如果我們去定義什麼是設計模式,我們會發現:

設計模式是常見問題的典型解決方案
在軟體設計中。每個圖案都像一張藍圖
您可以自訂以解決特定問題
程式碼中的設計問題。

那為什麼要發這篇文章呢?我的意思是,上面連結的網站上有很多資訊;這可能就是全部了。

問題是,我總是很難接受這個定義...「在我的程式碼中解決特定的設計問題」...在我的程式碼中?我的程式碼是否有需要解決的問題?

定義,重新構想

真正發生的事情是,我需要寫某個「東西」

,專案中使用的程式語言缺乏的抽象。

簡單明了。萬一您還沒有產生共鳴,讓我們來看看一些帶有程式碼的範例。

這是 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 月,以防萬一你忘記了) 新增了 Lambda(函數式程式設計的一個概念),所以我們可以這樣做:

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 實現,而且您是完全正確的,但不完全。

每種程式設計範式,特別是當採用最純粹的形式時,都有其怪癖、困難或“無法直線實現的事情”,如果你願意的話。

讓我們進入函數式程式設計領域。您可能聽過 Monad。

無論您是否陷入數學定義的思維陷阱,我們(軟體開發人員)也可以將 Monad 理解為設計模式。這是因為在純函數的世界裡,不會發生任何意外,很難想像副作用,但大多數軟體產品都需要副作用,那我們要如何...?

這是 Haskell 中 IO Monad 的範例:

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

Shape circle = shapeFactory.get("CIRCLE").get();
登入後複製
登入後複製

副作用(讀取檔)包含在 IO monad 中。

讓我們使用 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); 
登入後複製
登入後複製

一個經典的例子,我在網路上見過這個 Maybe monad 大約 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 monad 的解:

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 monad 在JavaScript 或typescript 中不是必需的,但如果你使用的語言沒有實現它......那麼,你可以應用Maybe monad或我該說設計模式嗎?

是的,我知道,有些人剛剛學會了Maybe 的東西,並熱切地將其立即應用到6 個副項目中,而現在我在聚會上因為告訴你“你不需要它”而咯咯笑。不過你仍然可以使用它,事實上,如果你覺得它很酷,我邀請你這樣做(在一天結束時,這是你的代碼,你可以做任何你想做的事!?)


但回到基礎。其他範式又如何呢?如果您跳出了 OOP/FP 的框框思考,我喜歡它!

所有範例肯定都有自己的重複出現的解決方案和技術,即使它們並不總是正式稱為「設計模式」。

這裡有一些例子(感謝雙子座避免了我的思考,感謝我漂亮的格式和附加價值?):

邏輯程式設計:
  • 約束邏輯程式設計:此範例涉及定義變數之間的限制和關係,然後讓系統找到滿足這些限制條件的解決方案。 回溯約束傳播等技術對於在此範式中有效解決問題至關重要。 (在處理人工智慧時非常有用)。
  • 演繹資料庫:這些資料庫使用邏輯規則和推理從現有資料中得出新資訊。 前向/後向連結等技術是這些資料庫如何運作的基礎,並且可以被視為此範例中的模式。
並發程式設計:
  • 訊息傳遞:在多個進程同時執行的並發系統中,訊息傳遞是通訊和協調的常用技術。 生產者-消費者讀者-作者等模式提供了用於管理對資源的並發存取並確保資料一致性的既定解決方案。
  • 同步原語:這些是低階結構,例如互斥體訊號量條件變數,用來控制對共享資源的訪問在並發程序中。雖然不是傳統意義上的“模式”,但它們代表了常見並發挑戰的明確定義的解決方案。

面向數據的程式設計:

  • 資料轉換管道:此範式強調透過一系列操作來轉換資料。像是mapfilterreduce 這樣的技術(在函數式程式設計中也很常見,自從加入以來在javascript 中使用得很多)是建構的基本構建塊這些管道,可以被視為此範式中的模式。
  • 實體組件系統(ECS):這種架構模式在遊戲開發和其他資料密集型應用程式中很流行。它涉及將實體分解為組件(數據)和系統(邏輯),促進數據局部性和高效處理。

有很多“技術”和“模式”,這個清單只是為了給你一些線索,如果你好奇的話。

希望您覺得本文有用,請盡快閱讀!

Demystifying Design Patterns


?總結一下,給急的人!

雖然「設計模式」一詞與 OOP 聯繫最緊密,但其他範式也有自己的一套重複出現的解決方案和技術。這些技術解決了這些範式的具體挑戰和限制,為常見問題提供了既定的方法。因此,即使它們並不總是被正式標記為“設計模式”,它們也具有類似的目的,可以指導開發人員找到有效且可維護的解決方案。

我們可以將設計模式理解為眾所周知的解決方法,用於修補我們正在使用的程式語言缺乏抽象的功能

這篇文章幾乎完全由我撰寫,指定範例由 Gemini 1.5 Pro

以上是揭秘設計模式的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:dev.to
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板