LoginSignup
7
3

More than 3 years have passed since last update.

TypeScript: デコレーターで実行時もイミュータブルなクラスを実現する

Posted at

TypeScriptでは、イミュータブルなクラスを作りたいときに、クラスプロパティにreadonlyをつけることで、読み取り専用のプロパティを定義できる。readonlyなプロパティに値を再代入しようとすると、コンパイルエラーになる。

TypeScriptコードはコンパイルされるとJavaScriptコードになるが、そのときにはreadonlyの情報は削除される。したがって、もともとreadonlyだったクラスプロパティは、JavaScriptコードになったときには上書き可能なプロパティになってしまう。

TypeScriptだけでソースコードを固められる場合は、readonlyつまりコンパイル時のイミュータビリティチェックだけで十分だと思う。一方で、TypeScriptで書いたクラスをJavaScriptライブラリとして提供し、かつ、そのクラスが誰に使われるか分からないような場合には、実行時もイミュータブルなクラスになっていないと不安が残る。

本稿では、デコレーターを使った、実行時イミュータブルなクラスの実現方法を紹介する。

実行時イミュータブルにするデコレータの定義

function immutable(constructor: any): any {
  const wrapper = function () {
    const instance = new constructor(...arguments)
    Object.freeze(instance)
    return instance
  }
  wrapper.prototype = Object.create(constructor.prototype)
  return wrapper
}

このデコレーター関数は、クラスコンストラクタを引数に受け取り、それを継承したプロキシコンストラクタを返すようになっている。
プロキシコンストラクタの中身は特に変わったことはしてなく、親コンストラクタが作ったインスタンスにObject.freezeをかけているだけだ。

デコレータの使い方

@immutable
class Sample {
  constructor(public readonly foo: number) {
  }
}

クラス定義に@immutableと書くだけ。

本当に実行時イミュータブルになっているかの確認

import assert = require('assert')

const sample = new Sample(0)

assert.strictEqual(sample.foo, 0)
assert.throws(
  () => eval(`sample.foo = 1`),
  /^TypeError: Cannot assign to read only property 'foo' of object '#<Sample>'$/
)
assert.strictEqual(sample.foo, 0)
7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3