どうも。つじけ(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>...
}
まとめ
以上で、「柔軟なインターフェイスのために、構造的な型付けを使う」をお届けしました。
次回は、「配列と「配列のようなもの」を区別しよう」 をお届けします。
参考資料
このシリーズの目次
- [EffectiveJavaScript輪読会8]一貫した規約を維持しよう
- [EffectiveJavaScript輪読会8]undefinedは「値なし」として扱う
- [EffectiveJavaScript輪読会8]オプションオブジェクトで、キーワード付き引数群を受け取ろう
- [EffectiveJavaScript輪読会8]不必要な状態を排除する
- [EffectiveJavaScript輪読会8]柔軟なインターフェイスのために、構造的な型付けを使う
- [EffectiveJavaScript輪読会8]配列と「配列のようなもの」を区別しよう
- [EffectiveJavaScript輪読会8]過剰な型強制を防ごう