iT邦幫忙

2024 iThome 鐵人賽

DAY 28
0

解釋器模式可以透過簡單語言來執行常見任務。

生活範例

一份樂譜記載著一段旋律,譜上的音符代表著音高,休止符代表著暫停,音節的長短則由不同的符號來表示。樂譜上的符號就像是一種音樂的語言。演奏家通過解讀樂譜,將這些靜態的符號轉化為動人的旋律,就像解釋器藉由解讀特定的語言來執行任務一樣。

舉個例子

在 Tailwind CSS 中,你可以使用官方提供的樣式名稱來套用樣式,也可以透過自訂的樣式名稱來客製化樣式。客製化樣式的建立方式非常簡單,只要將樣式或屬性用中括號包起來,系統就會依據這些自訂名稱產生對應的 CSS 。例如, w-[32rem] 會產生 .w\-\[32rem\] { width: 32rem; }

我們參考 Tailwind CSS 的客製化語法實作一個樣式名稱解釋器。使用者可以使用 屬性-[值] 的格式來指定樣式,只要樣式名稱符合規則,解釋器就會產生對應的 CSS。

Expression 是基本的介面,包含一個 interpret 方法,讓具體表達式定義節點的解析方法。

interface Expression {
  interpret(): string | undefined;
}

PropertyExpression 類別負責處理樣式名稱中的屬性縮寫(如 hw),將縮寫轉換為完整的屬性名稱。

class PropertyExpression implements Expression {
  private propertyMap: Record<string, string> = {
    h: "height",
    w: "width",
    bg: "background-color",
  };

  constructor(private propertyAlias: string) {}

  interpret() {
    return this.propertyMap[this.propertyAlias];
  }
}

ValueExpression 類別負責處理樣式名稱中的值(如 [12px]),interpret 方法會移除中括號,並返回其中的值。

class ValueExpression implements Expression {
  constructor(private value: string) {}

  interpret() {
    if (/\[[a-z0-9]+\]/.test(this.value)) {
      return this.value.slice(1, -1);
    }
  }
}

ClassNameExpression 類別負責解析完整的樣式名稱,將名稱轉換成對應的 CSS 語法。

class ClassNameExpression implements Expression {
  constructor(private value: string) {}

  interpret() {
    const [propAlias, value] = this.value.split("-");
    if (!propAlias || !value) return;

    const propExp = new PropertyExpression(propAlias);
    const valueExp = new ValueExpression(value);

    const interpretedProp = propExp.interpret();
    const interpretedValue = valueExp.interpret();

    if (interpretedProp && interpretedValue) {
      const escapedClassName = this.escapeClassName(this.value);
      return `.${escapedClassName} {\n  ${interpretedProp}: ${interpretedValue};\n}`;
    }
  }

  private escapeClassName(className: string): string {
    return className.replace(/[^a-z0-9]/g, (match) => `\\${match}`);
  }
}

ClassNameParser 負責用表達式解析字串,將樣式名稱轉換為 CSS。

class ClassNameParser {
  parse(value: string) {
    const classNames = value.trim().split(/\s+/);
    return classNames
      .map((className) => new ClassNameExpression(className).interpret())
      .filter(Boolean)
      .join("\n");
  }
}

定義一個解釋器並傳入樣式名稱來查看生成的結果。

class ClassNameParserTestDrive {
  static main() {
    const parser = new ClassNameParser();

    const className = "w-[12px] h-[12px] bg-[pink]";
    const result = parser.parse(className);

    console.log("CSS generated from custom class names:\n");
    console.log(result);
  }
}

ClassNameParserTestDrive.main();

執行結果:

CSS generated from custom class names:

.w\-\[12px\] {
  width: 12px;
}
.h\-\[12px\] {
  height: 12px;
}
.bg\-\[pink\] {
  background-color: pink;
}

定義

Interpreter Pattern

  • 抽象表達式(Expression): 定義節點的解析方法
  • 終結符表達式(TerminalExpression): 負責解析語言的基本元素
  • 非終結符表達式(NonTerminalExpression): 負責解析由多個符號組成的結構,如子句或片語

在某些情況下,我們會將常見的任務整理成一個簡單的語言,並透過這個語言來執行命令。解釋器模式可以讀取簡單語言,依據預先定義的規則解讀並執行相應的操作。這種做法可以減少重複性任務,使程式碼變得更有系統。此外,我們還能輕鬆地修改或增加文法規則來新增功能。解釋器模式適合用於特定領域的小型語言,面對文法複雜的大型語言,使用編譯器或轉譯器等技術會更為合適。

總結

  • 解釋器模式可以透過簡單語言來處理常見任務
  • 依據定義好的規則來解讀語法,並執行相應操作
  • 過於複雜的語法會增加解釋器的複雜度與維護成本

完整範例

https://github.com/chengen0612/design-patterns-typescript/blob/main/patterns/behavioral/interpreter.ts


上一篇
Day 27 - Method Chaining 方法鏈
下一篇
Day 29 - Visitor 訪問者
系列文
前端也想學設計模式30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言