6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

TypeScriptでValueObjectの表現を考えてみた

Posted at

TL;DR

abstract class ValueObject<T> {
  protected constructor(protected readonly val: T) {}

  get(): T {
    return this.val;
  }

  eq(vo: ValueObject<T>): boolean {
    if (this.constructor.name !== vo.constructor.name) return false;
    return this.get() === vo.get()
  }
}

abstract class StringValueObject extends ValueObject<string> {}

class UserName extends StringValueObject {
  static of(_val: string): UserName {
    return new this(_val);
  }
}

class AdminName extends StringValueObject {
  static of(_val: string): UserName {
    return new this(_val);
  }
}

const userName1 = UserName.of('rokumura')
const userName2 = UserName.of('rokumura')
const adminName = AdminName.of('rokumura')

console.log(userName1 == userName2)  // false
console.log(userName1.eq(userName2)) // true
console.log(userName1.eq(adminName)) // false

不変性

readonlyにすることで、値の変更を防ぐ。

protected constructor(protected readonly val: T) {}

Object.freezeを使っているサンプルも見かけたが、readonlyだけでも十分なのでは。

等価判定

上記のeqでは、コントラクタの名前を利用して、クラスが同じであることも条件に加えている。
単純に値が等価であるかだけを比較したければ、下記で十分。

eq(vo: ValueObject<T>): boolean {
  return this.get() === vo.get()
}

プリミティブの指定

他のプリミティブを指定することももちろん可能。

abstract class NumberValueObject extends ValueObject<number> {}
class Price extends NumberValueObject {
  static of(_val: number): Price {
    return new this(_val)
  }
}

バリデーション

バリデーションは、コンストラクタに仕込んでもいいし、抽象クラスに用意してもいいだろうし、ofの中に設けてもいいだろうし。

コンストラクタに仕込んだ場合

abstract class NumberValueObject extends ValueObject<number> {
  constructor(val: number) {
    if (val < 0) throw new Error('The value must be more than 0.')
    super(val)
  }
}

class Price extends NumberValueObject {
  static of(_val: number): Price {
    return new this(_val)
  }
}

const price1 = Price.of(100)
const price2 = Price.of(-1) // Error: The value must be more than 0.

抽象クラスに用意した場合

abstract class NumberValueObject extends ValueObject<number> {
  protected static validate(_val: number): void {
    if (_val < 0) throw new Error('The value must be more than 0.')
  }
}

class Price extends NumberValueObject {
  static of(_val: number): Price {
    super.validate(_val)
    return new this(_val)
  }
}

const price1 = Price.of(100)
const price2 = Price.of(-1) // Error: The value must be more than 0.

ofの中に設けた場合

abstract class NumberValueObject extends ValueObject<number> {}

class Price extends NumberValueObject {
  static of(_val: number): Price {
    if (_val < 0) throw new Error('The value must be more than 0.')
    return new this(_val)
  }
}

const price1 = Price.of(100)
const price2 = Price.of(-1) // Error: The value must be more than 0.

最後に

お寿司食べたい🍣

参考

6
4
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
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?