[EffectiveJavaScript輪読会8]柔軟なインターフェイスのために、構造的な型付けを使う

GAS

どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。

前回のおさらい

前回は、「不必要な状態を排除する」をお届けしました。

[EffectiveJavaScript輪読会8]不必要な状態を排除する
どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。前回のおさらい前回は、「オプションオブジェクトで...

今日は5回目で、「柔軟なインターフェイスのために、構造的な型付けを使う」をお届けします。

テキスト第6章「ライブラリとAPI設計」の項目57に対応しています。

今日のアジェンダ

  • Webページを生成しよう
  • フォーマッタ関数
  • Effectiveな型の実装

Webページを生成しよう

これは、架空の話です。

わたしたちユーザーが、WikipediaのWebページを作成するときは、このようなオブジェクト形式をリテラルに用意します。

このオブジェクト形式を、MEDIAWIKIフォーマット(仮) と呼ぶことにします。

  //MEDIAWIKIフォーマット(仮)に必要な3つのプロパティ
  {
    title: '',
    author: '',
    body: ''
  }

  //ユーザーがMEDIAWIKIフォーマット(仮)で作成したコンテンツ例
  const source = {
    title: 'クロージャ',
    author: 'KENZO TSUJI',
    body: `クロージャとは、**スコープチェーンを遡って**変数を参照する仕組みを持っている**関数**`
  }

Wikiアプリケーションを作るひとは、このフォーマットを解析する**フォーマッタ(関数)**を、Wikiクラスに渡します。(フォーマッタ関数については次節)

  //MEDIAWIKIフォーマッタ(Wiki.formats.MEDIAWIKI関数)を適用するためのインスタンス
  const app = new Wiki(Wiki.formats.MEDIAWIKI);

Wikiクラスの内部は、このように実装されるでしょう。

最終的に、displayPage()メソッドにより、HTMLが生成されます。

  /** クラスWiki */
  class Wiki {

    constructor(format) {
      this.format = format;
    }

    displayPage(source) {
      const page = this.format(source);
      const title = page.getTitle();
      const author = page.getAuthor();
      const outPut = page.toHTML();
      return outPut;
    }

  }

フォーマッタ関数

話が前後しますが、Wikipediaページを表示するために必要な、getTitle()、getAuthor()、toHTML()メソッドは、**フォーマッタ(関数)**に内蔵されています。

コンストラクタにフォーマッタ(関数)が渡されているために、この3つのメソッドが呼び出せると、考えてください。

では、Wiki.formats.MEDIAWIKI関数とは、どのように実装されているのでしょうか。

恐らく、sourceを受け取って、title、authorなどを解析できるような、オブジェクトが実装されているのでしょう。

このように、MWPageクラスのインスタンスをreturnする関数のことを、ファクトリー関数と呼びます。

  //静的プロパティ(MEDIAWIKIフォーマッタ(関数)の定義)
  Wiki.formats = {
    MEDIAWIKI(source) {

      /** クラスPage */
      class Page {
        constructor(source) {
          this.source = source;
        }

      }

      /** クラスMWPage */
      class MWPage extends Page {

        constructor(source) {
          super(source);
        }

        getTitle() { return this.source.title; }
        getAuthor() { return this.source.author; }
        toHTML() { return '<!doctype html> <html lang="ja"> <head>...'; }
      }

      return new MWPage(source);
    }

  }

最終的に、このように、メソッドを呼び出すことで、HTMLが生成されます。

  //レンダリング(HTMLを生成してWebページを表示する)処理
  console.log(app.displayPage(source)); // => <!doctype html> <html lang="ja"> <head>...

Effectiveな型の実装

この項目の、EffectiveなJavaScriptのポイントは、pageクラスと、Pageクラスを継承したMEDIAWIKIPageクラスの部分です。

Javaなどの静的型付け言語では、pageを表示するpageクラスを定義し、柔軟性の高いクラス(今回でいうMEDIAWIKIPageクラス)は継承して実装します。

ところが、JavaScriptは、クラスを自由に実装できる動的型付け言語です。

必要なプロパティは、getTitle()、getAuthor()、toHTML()メソッドなのですから、継承を用いなくても、実装できます。

  //静的プロパティ(MEDIAWIKIフォーマッタ(関数)の定義)
  Wiki.formats = {
    MEDIAWIKI(source) {

      const obj = {
        getTitle() { return source.title; },
        getAuthor() { return source.author; },
        toHTML() { return '<!doctype html> <html lang="ja"> <head>...'; }
      };

      return obj;
    }

  }

このように、「必要なプロパティさえあれば、それは、必要とした型でまちがいない」というプログラミング手法を、ダックタイピングと呼びます。

継承を用いた、階層構造による実装と、ダックタイピングによる構造的な型付けについては、別途ブログ化したいと思います。

最後に、全体像を掲載しておきます。

function myFunction57_02() {

  /** クラスWiki */
  class Wiki {

    constructor(format) {
      this.format = format;
    }

    displayPage(source) {
      const page = this.format(source);
      const title = page.getTitle();
      const author = page.getAuthor();
      const outPut = page.toHTML();
      return outPut;
    }

  }

  //静的プロパティ(MEDIAWIKIフォーマット(仮)の定義)
  Wiki.formats = {
    MEDIAWIKI(source) {

      const obj = {
        getTitle() { return source.title; },
        getAuthor() { return source.author; },
        toHTML() { return '<!doctype html> <html lang="ja"> <head>...'; }
      };

      return obj;
    }

  }


  //MEDIAWIKIフォーマットを適用するためのインスタンス
  const app = new Wiki(Wiki.formats.MEDIAWIKI);

  //ユーザーがMEDIAWIKIフォーマット(仮)で作成したコンテンツ
  const source = {
    title: 'クロージャ',
    author: 'KENZO TSUJI',
    body: `クロージャとは、**スコープチェーンを遡って**変数を参照する仕組みを持っている**関数**`
  }

  //レンダリング(HTMLを生成してWebページを表示する)処理
  console.log(app.displayPage(source)); // => <!doctype html> <html lang="ja"> <head>...

}

まとめ

以上で、「柔軟なインターフェイスのために、構造的な型付けを使う」をお届けしました。

次回は、「配列と「配列のようなもの」を区別しよう」 をお届けします。

参考資料

このシリーズの目次

タイトルとURLをコピーしました