演習問題解説
//TypeScriptではInterfaceを用いる(抽象クラスでは継承関係がややこしくなり実装が難しくなる)
interface A {
readonly message: string
}
class B implements A {
get message() {
return "HogeFuga"
}
}
type CConstructor = new (...args: any[]) => A
function C<T extends CConstructor>(Class: T){
return class extends Class {
constructor(...args: any[]) {
super(...args)
}
get loudMessage(): string{
return super.message.toUpperCase()
}
}
}
const D = C(B)
const d = new D()
デザインパターン
T ypeScript でデザインパターンの実装を 1 つか 2 つやらないのであれば、これはオブジェクト指向プログラミングに関する章とは言えません。そうでしょう?
プログラミング TypeScript 5章
- 広義にはパターン言語の形式に則って定められた各種の設計パターン。クラス設計のパターンからクラウドアーキテクチャのパターン、テスト設計パターンまで種々のパターンが存在する。
- 狭義には、GoFの23のデザインパターンを指す
GoFのデザインパターン
- 23の再利用可能な設計パターン集
- こういうときにこうする、みたいなものの集合です
- もう30年くらい前の設計パターンなので古い話も多いです
- とはいえ、一通り知っておくととてもよいです
- ちょうぜつエンジニアメモリーちゃんの記事が非常にまとまっていて良いです
こういう本がありますが、まあ表紙だけ覚えておきましょう(一応これが原典です)
(一番使いがちなやつです)
ファクトリパターン
- 複雑なオブジェクトの生成を別クラスに委譲することで、オブジェクトの生成と利用とを明確に分離する設計パターン。
- 今回は、Factoryそのものは単一だが、メソッドの引数によって生成されるオブジェクトを切り替えるパターン
- 利用者は 実際に何が作られたのかを意識させないのがポイント
ファクトリパターン
class User {}
interface IAPIHandler {
getUser: (id: string) => User
}
class APIHandler implements IAPIHandler {
getUser(id: string){
//TODO: fetch
return new User()
}
}
class MockAPIHandler implements IAPIHandler {
getUser(id: string){
return new User()
}
}
class APIFactory {
create(type: "mock" | "http") {
switch (type) {
case "mock" : {
return new MockAPIHandler()
}
case "http" : {
return new APIHandler()
}
}
}
}
ビルダーパターン
- 複雑なオブジェクトの作成を専用のオブジェクト(ビルダー)に委譲するパターン
- クラスの初期化が複雑化している場合などに、そのロジック自体を外だしするために使う
- 典型例: SQLやREST APIなどのクエリビルダ
ビルダーパターン
class APIBuilder {
private header?: Headers
private method?: "post" | "get" | "put"
private uri?: string
setHeader(header: Headers): APIBuilder{
this.header = header
return this
}
setMethod(method: "post" | "get" | "put"): APIBuilder{
this.method = method
return this
}
setUrl(url: string): APIBuilder{
this.url = url
return this
}
build(): APIHandler {
return new APIHandler()
}
}
演習問題
以下にビルダーパターンのサンプルコードを示します。
type Header = {
name: string,
value: string
}
type Method = "post" | "get" | "put" | "delete" | "patch"
class APIHandler {}
class APIBuilder {
private header?: Header[]
private method?: Method
private body?: object
private url?: string
setHeader(header: Header[]): APIBuilder{
this.header = header
return {...this}
}
setMethod(method: Method): APIBuilder{
this.method = method
return {...this}
}
setBody(body: object){
this.body = body
return {...this}
}
setUrl(url: string): APIBuilder{
this.url = url
return {...this}
}
build(): APIHandler {
//TODO
return new APIHandler()
}
}
const api = new APIBuilder()
.setUrl("https://api.github.com/users/octocat")
.setMethod("get")
.build()
このAPIビルダーには、「未設定の項目がある状態でビルドできてしまう」とあいう弱点があります。
build
メソッドで実行時エラーを発生させてもいいのですが、本来ならば未設定の項目がある場合にコンパイルが通らないほうが好ましいです。
そこで、以下の動作を実現させてみてください。
最低限MethodとUrlが指定されていないと、buildメソッドが呼べないようにする。言い換えると、setMethodとsetUrlが呼ばれていない場合、buildを呼んでもコンパイルエラーとなるようにする。
[応用]メソッドがpostに設定された場合、bodyが設定されていないとbuildメソッドが呼べないようにする
実現のためには、メソッドにおける隠されたthisパラメータ を利用するのが重要になります。