TypeScriptでクラス思考と関数思考のギャップを少し埋める

33375 ワード

クラスのコンストラクタと関数

person.ts
class Person {
  private name: string;
  
  private age: number;
  
  constructor(name: sring, age: number){
    this.name = name;
    this.age = age;
  }
  
  selfIntroduce(): string {
    return `Hi! I'm ${this.name}!`;
  }
  
  isAdult(): boolean {
    return this.age >= 18;
  }
}

const person = new Person('takeshi', 20);
const introduce = person.selfIntroduce();
const isAdult = person.isAdult();

selfIntroduceはageつかってないし、isAdultはnameつかってない。
コンストラクタを依存とするなら、それぞれのメソッドに使ってない依存があることになる。
そこで、これを1つのメソッドにつき1つのクラスにする。

selfIntroduce.ts
class selfIntroduce {
  private name: string;
  
  constructor(name: sring){
    this.name = name;
  }
  
  do(): string {
    return `Hi! I'm ${this.name}!`;
  }
}

const introduce = new selfIntroduce('takeshi').do();
isAdult.ts
class isAdult {
  private age: number;
  
  constructor(age: number){
    this.age = age;
  }
  
  do(): boolean {
    return this.age >= 18;
  }
}

const isAdult = new isAdult(20).do();

毎回コンストラクタを挟むのも、ファイルを別にする(1クラス1ファイルというプラクティスがある)のもめんどいので関数にする。

export const selfIntroduce = (name: string) => {
  return `Hi! I'm ${this.name}!`;
}

export const isAdult = (age: number) => {
  return this.age >= 18;
}

JSにおけるビルダーと関数のカリー化

JSは関数を返り値として持てる(関数が第一級オブジェクトである)ので、次のようなビルダーを作成できる。

/**
 * hogeとfugaをいれて、最後に連結させて返す。
 */
const hogeFugaBuilder = (hoge: string) => (fuga: string) => {
  return `${hoge}${fuga}`;
}

// これでも同じ意味。関数を返す関数。
const hogeFugaBuilder = (hoge: string) => {
  return (fuga: string) => {
    return `${hoge}${fuga}`;
  }
}

// こう使う
hogeFugaBuilder('hoge!')('fuga!') // hoge!fuga!;

べつにこのようなことをしなくても、一気に初期化すればいいと思うかもしれない。

hogeFuga.ts
const hogeFuga = (hoge: string, fuga: string) => {
  return `${hoge}${fuga}`;
}

// こう使う 
hogeFuga('hoge!', 'fuga!') // hoge!fuga!

hogeFugaBuilder関数とhogeFuga関数の違いはなんだろうか?それはビルダーパターンのほうは、依存に時間差をつけることができることだ。すぐにhogeは決まるけど、fugaはフローの後の方で入れたいとかに使える。またさらによいことに1つ目の依存を固定した新しい関数を作りやすい。この引数の1つ目の依存さえ決めればとりあえず成り立つということが関数の再利用性と取り回しやすさを向上させている。

const mochiFuga = hogeFugaBuilder('mochi');
const tamaFuga = hogeFugaBuilder('tama');

mochiFuga('fuga!!!') // mochifuga!!!
tamaFuga('fuga!!!') // tamafuga!!!

関数型言語だとこれらはカリー化とよばれ、すべての関数がデフォルトでビルダーパターンになっている。

クラスのデコレータと高階関数

クラスにデコレータがある言語もある。TypeScriptもデコレータが使える。デコレーターをつけたクラスやメソッドの情報を受け取って加工して返したり、メタデータに一時的な変数を記録しておいたりする。