インタフェースを読み書きする機会が増えた、と思う。で、自分なりにコードリーディングを頑張っていると、インタフェースは(TypeScriptにおけるtypeも含めて)以下の3種類に整理できるのかな、というイメージが湧いたのでメモしてみる。
ポリモーフィズムの明示
ポリモーフィズムを実現するには、共通の契約が必要。
たとえば、以下のようなUserクラスがあったとして、継承による実現なら抽象クラスが使われるが…
class VipUser extends User {
badge(): string {
return "VIP"
}
canPost(): boolean {
return true
}
}
class TrialUser extends User {
badge(): string {
return "TRIAL"
}
canPost(): boolean {
return new Date() > this.trialEndsAt
}
}
コンポジションによって行われるなら、かれらが共通のイデアを持つということは、それぞれの実装が共通のインタフェースに依存する、という形で示される。
interface UserIdentity {
id: UserId
}
interface UserTier {
badge(): string
}
interface PostPermission {
canPost(): boolean
}
interface UserRole extends HasUserId, UserTier, PostPermission {}
class VipUserRole implements UserRole {
constructor(public readonly id: string) {}
badge(): string {
return "VIP"
}
canPost(): boolean {
return true
}
}
class TrialUserRole implements UserRole {
constructor(
public readonly id: string,
private readonly trialEndsAt: Date
) {}
badge(): string {
return "TRIAL"
}
canPost(): boolean {
return new Date() > this.trialEndsAt
}
}
レイヤー間の依存関係の明示
依存性逆転の法則を実装するのに使う。これは往々にしてレイヤー間の処理に使われて、インタフェースがどちらで定義されているかでレイヤー間の依存性がわかる。
無論これは非常に単純化した話で、それだけで依存性がわかるほどシンプルなコードは少ないとはいえ、誰のワガママが表現されているのか(フロントかバックか?DomainかRepositoryか?)を読み取れるとコードリーディングがスムーズになりそうだ。
export interface GetPostResult {
postId: string
body: string
authorId: string
}
export class GetPostUseCase {
...
}
import type { GetPostResult } from "../../../application/usecases/GetPostUseCase"
export function toPostResponse(input: {
post: GetPostResult
authorDisplayName: string
}): PostResponse {
return {
id: input.post.postId,
body: input.post.body,
authorDisplayName: input.authorDisplayName,
}
}
抽象に依存させよ(https://speakerdeck.com/akinoriakatsuka/ditutenandakanan-sii-yi-cun-toiugai-nian-wo-shi-ushi-wareru-toiuyan-xie-dezheng-li-siyou)
命名による責任の明示
PythonのdataClassなど。上2つとは少し毛色が違う。上記2つは「抽象的である(=具体的な実装を持たない)」という性質を利用しているのに対し、こちらは「名前を持っている」という性質を利用している。
interface NotificationPayload {
userId: string
title: string
body: string
}
function sendNotification(payload: NotificationPayload) {
}
データや処理のまとまりに名前をつけたいときに、無名オブジェクトや位置引数の代わりに、ラベルシールを貼るような気持ちでinterfaceを導入するときがあるということ。(Classもこの用法で使われることが少なくないだろう)
TypeScriptはinterfaceとtypeのどちらもこの用法が用いられる印象がある。