Upgrade to Pro — share decks privately, control downloads, hide ads and more …

スーパークラスかインターフェースか

 スーパークラスかインターフェースか

プログラミングの現場で、顧客の要求する仕様をクラス設計していくと、共通する機能が出てくることがある。
それを継承関係を作ってスーパークラスに実装するのか、インターフェースにするのか、どちらがよいのかという疑問に答える。
より現場にありそうな問題を取り上げて、ドメイン駆動設計の入り口まで行く。

<対象者>
・プログラミングを始めて2,3年以上
・オブジェクト指向でプログラミングをしている
・オブジェクト指向のプログラミングを理解はできるし、自分で書いているが、うまく書けているか自信がない

Goto Akiko

August 09, 2024
Tweet

Other Decks in Programming

Transcript

  1. 4

  2. 5 さる チンパンジー ねこ さる チンパンジー ねこ 運動能 力 100~200

    80~180 300~400 知能 150~200 200~300 50 鳴き声 ウキー ウキー ニャー
  3. 6 抽象スーパークラス 動物 (Animal) カッコ内はクラス名 抽象スーパークラス 猿 (Ape) クラス 猿

    (Monkey) クラス チンパンジー (Chimpanzee) クラス 猫 (Cat) 簡単っす! 白河くん 発田くん 中条さん 若手プログラマーの皆さん(仮想のキャラクターです)
  4. 7 abstract class Animal { name : string intelligence :

    number exercise_ability: number constructor(name: string, intelligence: number, exercise_ablility: number) { this.name = name; this.intelligence = intelligence; this.exercise_ability = exercise_ablility; } abstract bark(): void } abstract class Ape extends Animal { bark(): void { console.log('ウキー') } } class Monkey extends Ape { } //CatクラスとChimpanzeeクラスは省略 コードはTypeScriptで書いていきます。
  5. 8 さる チンパンジー ねこ さる チンパンジー ねこ 運動能 力 100~200

    80~180 300~400 知能 150~200 200~300 50 鳴き声 ウキー ウキー ニャー 高所から落ちたとき 運動能力が150以上の動物は 空中で1回転できる 知能が180以上の動物は 絵を描くことができる
  6. 9

  7. 10 最初のクラス設計は保 ちながら、インスタンス 化するコントローラーで 判定をします! 白河くん (仮想のキャラクターと仮想の回答例です。) 高所から落ちたとき 運動能力が150以上の動物は 空中で1回転できる

    知能が180以上の動物は 絵を描くことができる 実装したい機能 抽象スーパークラス 動物 (Animal) カッコ内はクラス名 抽象スーパークラス 猿 (Ape) クラス 猿 (Monkey) クラス チンパンジー (Chimpanzee) クラス 猫 (Cat)
  8. 11 abstract class Animal { name : string intelligence :

    number exercise_ability: number //コンストラクタはP5と同様なので省略 abstract bark(): void draw(): void { console.log('絵を描いたよ') } } // MonkeyクラスとChimpanzeeクラスはP5と同様なので省略 const animal1 = new Monkey('エテ吉', 180, 180) const animal2 = new Chimpanzee('ジョージ', 250, 90) if(animal1.intelligence >= 180) { animal1.draw() } 白河くん
  9. 12 高所から落ちたとき 運動能力が150以上の動物は 空中で1回転できる 知能が180以上の動物は 絵を描くことができる 実装したい機能 そしたら4足歩行動物というインター フェースを作って、空中で一回転できる とか、絵を描けるとかはインターフェー

    スに実装します。 インターフェースにした方がテストも書 きやすいし実装が軽くてよいとよく聞き ます。 quadrupedal animals インターフェース 発田くん クラス 猿 (Monkey) クラス チンパンジー (Chimpanzee) クラス 猫 (Cat)
  10. 13 発田くん class Chimpanzee implements QuadrupedalAnimals { // プロパティ、コンストラクタは省略 isDrawable():

    boolean { if(this.intelligence >= 180) { return true; } return false; } isSpinable(): boolean { if(this.exercise_ability >= 150) { return true; } return false; } spin() { } draw() { console.log('絵を描いたよ') } } interface QuadrupedalAnimals{ name: string exercise_ability: number isSpinable():boolean spin():void isDrawable():boolean draw():void } const animal = new Chimpanzee('ジョージ', 250, 100) if(animal.isDrawable()){ animal.draw() }
  11. 14 猿は絵を描くことができる猿もいる、 しかし猫はどんな猫でも 絵を描くことができない。 現実では当たり前のことなので、 それらをコードに落とし込みます。 それがDDDだと思います。 中条さん (仮想のキャラクターと仮想の回答例です。) 高所から落ちたとき

    運動能力が150以上の動物は 空中で1回転できる 知能が180以上の動物は 絵を描くことができる 実装したい機能 抽象スーパークラス 動物 (Animal) 抽象スーパークラス 猿 (Ape) クラス 猿 (Monkey) クラス チンパンジー (Chimpanzee) クラス 猫 (Cat)
  12. 15 中条さん export abstract class Ape extends Animal { bark():

    void { console.log('ウキ-') } fall(): void { if (this.exercise_ability > 150) { this.turn_around() } else { this.fall_down() } } abstract turn_around(): void abstract fall_down(): void } export class Cat extends Animal { bark(): void { console.log('ニャー') } draw(): void { throw new Error('猫は絵が描けません') } fall(): void { this.turn_around() } turn_around(): void { console.log('1回転して華麗に着地') } } export class Chimpanzee extends Ape { private readonly items: string[] = [] constructor(name: string, intelligence: number, exercise_ablility: number) { super(name, intelligence, exercise_ablility) this.items.push('バナナ', '木の実', '昆虫') } draw(): void { console.log('絵が描けます。') } turn_around(): void { console.log('1回転して華麗に着地') } fall_down(): void { console.log('ドサッと落ちる') } }
  13. 17 車を100台以上保有していますが、車検・保険・メンテナンス などの管理がとても煩雑です。 今は、地方の営業所ごとにExcelで担当者が管理しています が、効率が悪いのでシステムを作って効率化したいのです。 車検・保険が切れないようにしたいので、車検や保険が切れ る前に担当者へメールで通知してほしいんですよね。そのタ イミングは、車検は半年前と3か月前と1か月前、保険類は3 か月前とかがよいです。 また、車両のメンテナンスについては車検以外に定期点検と

    言って車の種類ごとに、3か月単位や6か月単位で行う点検 があります。 その点検を怠らないように、これも点検の3日前にはメールで 担当者に通知を行いたいです。 保険については、とりあえず、自賠責保険と任意保険の2種 類でよいです。保険は、事故の際に緊急連絡先がすぐ取り出 せるようにしたいですね。他にも… 顧客
  14. 18 保険や車検・定期点検の切れる前に それぞれのタイミングでメール通知 保険は緊急連絡先がすぐ取り出せる ようにしたい 実装したい機能 車検は半年前・3か月前 1か月前にメール通知 決まった実装内容 ・毎日定時にプログラムを走らせて、そ

    の時間はメール通知が必要な時間か調 べる ・一旦メール通知したら、次の通知タイミ ングをDBに格納しておく ・タイミングは日付で管理 (本来はUnixTimeなどの方がよいが、サ ンプルコードの可読性のため日付) ・DBへの読み書きは既存の機能で存在 するので必要ない ・車検の有効期限などは別のプログラ ムで更新されるので今回は実装しなくて よい
  15. 19 export class NotifierForInspectionAndInsurance { private category: string private email:

    string private expire_date: string private notify_timing: string private emergency_contact?: string | undefined constructor(category: string, email: string, expire_date: string, notify_timing: string, emergency_contact?: string) { this.category = category this.email = email this.expire_date = expire_date this.notify_timing = notify_timing this.emergency_contact = emergency_contact } notify(): void { this.sendEmail(this.email, '通知処理') this.setNextNotifyTiming() } isNotifyTiming(): boolean { const currentDate = this.formatDateToYYYYMMDD(new Date()); if (currentDate == this.notify_timing) { return true; } else { return false; } } sendEmail(email: string, message: string): void { /* 通知処理 省略 */ } formatDateToYYYYMMDD(date: Date): string { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } getEmergencyContact(): string | undefined { return this.emergency_contact } setNextNotifyTiming(): void { if (this.category === '車検') { // 車検の場合は、expire_dateの1ヶ月前、3か月前、半年前に次の通知タ イミングをセット const today = new Date(); const one_month_before = this.calculateDateBefore(1); const three_months_before = this.calculateDateBefore(3); const six_months_before = this.calculateDateBefore(6); if (today < six_months_before) { console.log('今日は6か月前以前です'); this.notify_timing = this.formatDate(six_months_before); } else if (today >= six_months_before && today < three_months_before) { console.log('今日は3か月~6か月の間です'); this.notify_timing = this.formatDate(three_months_before); } else if (today >= three_months_before && today < one_month_before) { console.log('今日は3月~1か月前です'); this.notify_timing = this.formatDate(one_month_before); } } else if (this.category === '大型車定期点検' || this.category === '普通車定 期点検') { // 定期点検の場合は、まず次の点検タイミングをセット 大型車は3か月 、普通車は6か月 省略 } } //省略 } index.ts (エントリーポイント) 左のボックスの続き const car_inspection = new NotifierForInspectionAndInsurance( '車検', '[email protected]', '2024-11-03', '2024-08-04') if(car_inspection.isNotifyTiming()){ car_inspection.notify() }
  16. 20

  17. 22 保険 自賠責 保険 任意保険 点検 車検 定期点検 これで 理解はあって

    ますか? 重要ポイント: 構造図はわかりやすいの で顧客と会話できる! あってる
  18. 23 抽象スーパークラス 保険 (Insurance) クラス自賠責保険 (Mandatory Insurance) クラス任意保険 (Optional Insurance)

    抽象スーパークラス 点検(Inspection) クラス車検 (CarInspection) クラス定期点検 (Periodic Inspection) 保険 自賠責 保険 任意 保険 点検 車検 定期 点検
  19. 24 抽象スーパークラス 保険 (Insurance) クラス自賠責保険 (Mandatory Insurance) クラス任意保険 (Optional Insurance)

    抽象スーパークラス 点検 (Inspection) クラス車検 (CarInspection) クラス定期点検 (Periodic Inspection) <共通しそうな機能> ・通知を送る ・通知を送ったら次の通知タ イミングをセットする
  20. 25

  21. 26 抽象スーパークラス 保険・点検類 (InsuaranceAndInspection) 抽象スーパークラス 保険 (Insurance) クラス 自賠責保険 (MandatoryInsurance)

    クラス 任意保険 (OptionalInsurance) 抽象スーパークラス 点検 (Inspection) クラス 車検 (CarInspection) クラス 定期点検 (PeriodicInspection) 保険とメンテナンスは 有効期限がある、 任意のタイミングで通知する など共通する概念のものだと思 います。 だからスーパークラスを作って そこに実装するのがよいと思い ます。 スーパー クラス追加
  22. 27 左のボックスの続き index.ts (エントリーポイント) export abstract class InsuranceAndInspection { protected

    email : string protected expire_date: string protected notify_timing: string constructor(email: string, expire_date: string, notify_timing: string) { this.email = email this.expire_date = expire_date this.notify_timing = notify_timing } notify(): void { this.sendEmail(this.email, '通知処理') this.setNextNotifyTiming() } isNotifyTiming(): boolean { const currentDate = this.formatDate(new Date()); return currentDate == this.notify_timing } abstract setNextNotifyTiming(): void // sendEmail(),calculateDateBefore(),formatDate()は省略 } export abstract class Insurance extends InsuranceAndInspection { protected emergency_contact: string constructor(email: string, expire_date: string, notify_timing: string, emergency_contact: string) { super(email, expire_date, notify_timing) this.emergency_contact = emergency_contact } getEmergencyContact(): string { return this.emergency_contact } } export abstract class Insurance extends InsuranceAndInspection { protected emergency_contact: string constructor(email: string, expire_date: string, notify_timing: string, emergency_contact: string) { super(email, expire_date, notify_timing) this.emergency_contact = emergency_contact } getEmergencyContact(): string { return this.emergency_contact } } export abstract class Inspection extends InsuranceAndInspection { } export class CarInspection extends Inspection { setNextNotifyTiming(): void { // expire_dateの1ヶ月前、3か月前、半年前に次の通知タイミングをセット const today = new Date(); const one_month_before = this.calculateDateBefore(1); const three_months_before = this.calculateDateBefore(3); const six_months_before = this.calculateDateBefore(6); if (today < six_months_before) { console.log(‘今日は6か月前以前です’); this.notify_timing = this.formatDate(six_months_before); } // 以下、1ヶ月前、3か月前に次の通知タイミングをセットするコード 省略 } const car_inspection = new CarInspection( '[email protected]', '2024-11-03', '2024-08-04') if(car_inspection.isNotifyTiming()){ car_inspection.notify() }
  23. 28 保険と点検は別物じゃないです か?? 有効期限が切れる、 任意のタイミングで通知する は機能であって、構造ではない。 そこでインターフェースにするのが よいと思います。 有効期限を確認して通知するイン ターフェース定義して保険と

    点検クラスに実装します。 抽象スーパークラス 保険 (Insurance) クラス自賠責保険 (Mandatory Insurance) クラス任意保険 (Optional Insurance) 抽象スーパークラス 点検(Inspection) クラス車検 (CarInspection) クラス定期点検 (Periodic Inspection) 有効期限を 確認して通知するインターフェース (NotifyByNeed) インターフェース追加
  24. 29 右上に続く export interface NotifyAndSetNextTiming { notify(): void isNotifyTiming(): boolean

    setNextNotifyTiming(): void } export abstract class Insurance implements NotifyAndSetNextTiming{ protected email : string protected expire_date: string protected notify_timing: string protected emergency_contact: string constructor(email: string, expire_date: string, notify_timing: string, emergency_contact: string) { this.email = email this.expire_date = expire_date this.notify_timing = notify_timing this.emergency_contact = emergency_contact } notify(): void { this.sendEmail(this.email, '通知処理') this.setNextNotifyTiming() } isNotifyTiming(): boolean { const currentDate = this.formatDate(new Date()); return currentDate == this.notify_timing } getEmergencyContact(): string { return this.emergency_contact } abstract setNextNotifyTiming(): void // sendEmail(),calculateDateBefore(),formatDate()は省略 } export abstract class Inspection implements NotifyAndSetNextTiming { protected email : string protected expire_date: string protected notify_timing: string constructor(email: string, expire_date: string, notify_timing: string) { this.email = email this.expire_date = expire_date this.notify_timing = notify_timing } notify(): void { this.sendEmail(this.email, ‘通知処理’) this.setNextNotifyTiming() } isNotifyTiming(): boolean { const currentDate = this.formatDate(new Date()); return currentDate == this.notify_timing } abstract setNextNotifyTiming(): void // sendEmail(),calculateDateBefore(),formatDate()は省略 } export class CarInspection extends Inspection { inspect(): boolean{ /* 点検処理 省略 */ return true } setNextNotifyTiming(): void { const today = new Date(); const one_month_before = this.calculateDateBefore(1); const three_months_before = this.calculateDateBefore(3); const six_months_before = this.calculateDateBefore(6); if (today < six_months_before) { console.log('今日は6か月前以前です'); this.notify_timing = this.formatDate(six_months_before); } } } エントリーポイント省略
  25. 30

  26. 31 左のボックスの続き index.ts (エントリーポイント) export abstract class InsuranceAndInspection { protected

    email : string protected expire_date: string protected notify_timing: string constructor(email: string, expire_date: string, notify_timing: string) { this.email = email this.expire_date = expire_date this.notify_timing = notify_timing } notify(): void { this.sendEmail(this.email, '通知処理') this.setNextNotifyTiming() } isNotifyTiming(): boolean { const currentDate = this.formatDate(new Date()); return currentDate == this.notify_timing } abstract setNextNotifyTiming(): void // sendEmail(),calculateDateBefore(),formatDate()は省略 } export abstract class Insurance extends InsuranceAndInspection { protected emergency_contact: string constructor(email: string, expire_date: string, notify_timing: string, emergency_contact: string) { super(email, expire_date, notify_timing) this.emergency_contact = emergency_contact } getEmergencyContact(): string { return this.emergency_contact } } export abstract class Insurance extends InsuranceAndInspection { protected emergency_contact: string constructor(email: string, expire_date: string, notify_timing: string, emergency_contact: string) { super(email, expire_date, notify_timing) this.emergency_contact = emergency_contact } getEmergencyContact(): string { return this.emergency_contact } } export abstract class Inspection extends InsuranceAndInspection { } export class CarInspection extends Inspection { setNextNotifyTiming(): void { // expire_dateの1ヶ月前、3か月前、半年前に次の通知タイミングをセット const today = new Date(); const one_month_before = this.calculateDateBefore(1); const three_months_before = this.calculateDateBefore(3); const six_months_before = this.calculateDateBefore(6); if (today < six_months_before) { console.log(‘今日は6か月前以前です’); this.notify_timing = this.formatDate(six_months_before); } // 以下、1ヶ月前、3か月前に次の通知タイミングをセットするコード 省略 } const car_inspection = new CarInspection( '[email protected]', '2024-11-03', '2024-08-04') if(car_inspection.isNotifyTiming()){ car_inspection.notify() } プロパティがnull 許可にならずに すんでいる if文の分岐になら ずに済んでいる
  27. 32 左のボックスの続き index.ts (エントリーポイント) export abstract class InsuranceAndInspection { protected

    email : string protected expire_date: string protected notify_timing: string constructor(email: string, expire_date: string, notify_timing: string) { this.email = email this.expire_date = expire_date this.notify_timing = notify_timing } notify(): void { this.sendEmail(this.email, '通知処理') this.setNextNotifyTiming() } isNotifyTiming(): boolean { const currentDate = this.formatDate(new Date()); return currentDate == this.notify_timing } abstract setNextNotifyTiming(): void // sendEmail(),calculateDateBefore(),formatDate()は省略 } export abstract class Insurance extends InsuranceAndInspection { protected emergency_contact: string constructor(email: string, expire_date: string, notify_timing: string, emergency_contact: string) { super(email, expire_date, notify_timing) this.emergency_contact = emergency_contact } getEmergencyContact(): string { return this.emergency_contact } } export abstract class Insurance extends InsuranceAndInspection { protected emergency_contact: string constructor(email: string, expire_date: string, notify_timing: string, emergency_contact: string) { super(email, expire_date, notify_timing) this.emergency_contact = emergency_contact } getEmergencyContact(): string { return this.emergency_contact } } export abstract class Inspection extends InsuranceAndInspection { } export class CarInspection extends Inspection { setNextNotifyTiming(): void { // 車検の場合は、expire_dateの1ヶ月前、3か月前、半年前に次の通知タイミングを セット const today = new Date(); const one_month_before = this.calculateDateBefore(1); const three_months_before = this.calculateDateBefore(3); const six_months_before = this.calculateDateBefore(6); if (today < six_months_before) { console.log(‘今日は6か月前以前です’); this.notify_timing = this.formatDate(six_months_before); } // 以下、1ヶ月前、3か月前に次の通知タイミングをセットするコード 省略 } const car_inspection = new CarInspection( '[email protected]', '2024-11-03', '2024-08-04') if(car_inspection.isNotifyTiming()){ car_inspection.notify() } 名前が長いし違 うものを一緒にし ている感じがする
  28. 33 右上に続く export interface NotifyAndSetNextTiming { notify(): void isNotifyTiming(): boolean

    setNextNotifyTiming(): void } export abstract class Insurance implements NotifyAndSetNextTiming{ protected email : string protected expire_date: string protected notify_timing: string protected emergency_contact: string constructor(email: string, expire_date: string, notify_timing: string, emergency_contact: string) { this.email = email this.expire_date = expire_date this.notify_timing = notify_timing this.emergency_contact = emergency_contact } notify(): void { this.sendEmail(this.email, '通知処理') this.setNextNotifyTiming() } isNotifyTiming(): boolean { const currentDate = this.formatDate(new Date()); return currentDate == this.notify_timing } getEmergencyContact(): string { return this.emergency_contact } abstract setNextNotifyTiming(): void // sendEmail(),calculateDateBefore(),formatDate()は省略 } export abstract class Inspection implements NotifyAndSetNextTiming { protected email : string protected expire_date: string protected notify_timing: string constructor(email: string, expire_date: string, notify_timing: string) { this.email = email this.expire_date = expire_date this.notify_timing = notify_timing } notify(): void { this.sendEmail(this.email, ‘通知処理’) this.setNextNotifyTiming() } isNotifyTiming(): boolean { const currentDate = this.formatDate(new Date()); return currentDate == this.notify_timing } abstract setNextNotifyTiming(): void // sendEmail(),calculateDateBefore(),formatDate()は省略 } export class CarInspection extends Inspection { inspect(): boolean{ /* 点検処理 省略 */ return true } setNextNotifyTiming(): void { const today = new Date(); const one_month_before = this.calculateDateBefore(1); const three_months_before = this.calculateDateBefore(3); const six_months_before = this.calculateDateBefore(6); if (today < six_months_before) { console.log('今日は6か月前以前です'); this.notify_timing = this.formatDate(six_months_before); } } } エントリーポイント省略 重複するプロパ ティ、内容が同じ メソッドが多く存 在している
  29. 36 • シンプルな機能 • 通知、クリックできる、走ることができる、など • 汎用的 • 色々なプログラムで使いそう •

    ◦◦ しないといけない、◦◦できる、◦◦を 持っている などと言い表せる なぜか?
  30. 37 • インターフェースは実装を書けないので(大体 の言語において)小さい機能の実装が適して いる • 取り外ししやすい • その機能がいらなくなる時に外すのが簡単 •

    逆につけたい場合に、つけるのが簡単 • クラスに複数のインターフェースを実装することが可能 なので、複雑な機能をインターフェースに実装すると、 混乱しやすい
  31. 38

  32. 40 抽象スーパークラス 保険・点検類 (InsuaranceAndInspection) 抽象スーパークラス 保険 (Insurance) クラス 自賠責保険 (MandatoryInsurance)

    クラス 任意保険 (OptionalInsurance) 抽象スーパークラス 点検 (Inspection) クラス 車検 (CarInspection) クラス 定期点検 (PeriodicInspection) 有効期限を 確認して通知するインターフェース (NotifyByNeed) インターフェース追加 スーパー クラス追加
  33. 41 伝わりはするが クラスの名前が よくない 一番この分野に 詳しい人に相談する この場合は顧客 抽象スーパークラス 保険・点検類 (InsuaranceAndInspection)

    抽象スーパークラス 保険 (Insurance) クラス 自賠責保険 (MandatoryInsurance) クラス 任意保険 (OptionalInsurance) 抽象スーパークラス 点検 (Inspection) クラス 車検 (CarInspection) クラス 定期点検 (PeriodicInspection) 有効期限を 確認して通知するインターフェース (NotifyByNeed)
  34. 43

  35. 44 重要ポイント: 構造図はわかりやすいの で顧客と会話できる! 以前お見せした構造 図をちょっと変えて、保 険と点検をまとめよう と思っています。 保険と点検をまとめた 用語がありますか?

    それなら「メンテナン ス」だね。 弊社では、今Excelで 管理しているんだけど、 「メンテナンス台帳」 というファイル名なん だ。 保険・点検類 保険 自賠責保険 任意保険 点検 車検 定期点検
  36. 46 抽象スーパークラス メンテナンス (Maintenance) 抽象スーパークラス 保険 (Insurance) クラス 自賠責保険 (MandatoryInsurance)

    クラス 任意保険 (OptionalInsurance) 抽象スーパークラス 点検 (Inspection) クラス 車検 (CarInspection) クラス 定期点検 (PeriodicInspection) 有効期限を 確認して通知するインターフェース (NotifyByNeed) メンテナンスという名前に変更
  37. 47 なるほど、保険と点検は別物じゃないか と思っていましたが、「メンテナンス」と言 われれば、確かに同じ概念だと腑に落 ちます! そこで思いついたのが、「メンテナンス」 というのは「ほったらかしにしておくわけ にはいかない」というモノだと思います。 つまり、有効期限があったり通知する必 要があるということだと思います。

    そこで、通知タイミングに関する機能は メンテナンスクラスに実装しておけばわ かりやすいんじゃないでしょうか? メンテナンス (Maintenance) 保険 (Insurance) 自賠責保険 (MandatoryInsurance) 任意保険 (OptionalInsurance) 点検 (Inspection) 車検 (CarInspection) 定期点検 (PeriodicInspection)
  38. 48 すっきりして わかりやすい! 削除 抽象スーパークラス メンテナンス (Maintenance) 抽象スーパークラス 保険 (Insurance)

    クラス 自賠責保険 (MandatoryInsurance) クラス 任意保険 (OptionalInsurance) 抽象スーパークラス 点検 (Inspection) クラス 車検 (CarInspection) クラス 定期点検 (PeriodicInspection) 有効期限を確認して 通知するインターフェース (NotifyByNeed) 通知のタイミングを計るメ ソッドはMaintenanceクラス へ移動
  39. 49 左のボックスの続き index.ts (エントリーポイント) export abstract class Insurance extends Maintenance

    { protected emergency_contact: string constructor(email: string, expire_date: string, notify_timing: string, emergency_contact: string) { super(email, expire_date, notify_timing) this.emergency_contact = emergency_contact } getEmergencyContact(): string { return this.emergency_contact } } export class CarInspection extends Inspection { inspect(): boolean{ /* 点検処理 省略 */ return true } setNextNotifyTiming(): void { // 車検の場合は、expire_dateの1ヶ月前、3か月前、半年前に次の通知タイミングをセット const today = new Date(); const one_month_before = this.calculateDateBefore(1); const three_months_before = this.calculateDateBefore(3); const six_months_before = this.calculateDateBefore(6); if (today < six_months_before) { console.log('今日は6か月前以前です'); this.notify_timing = this.formatDate(six_months_before); }// 以下、1ヶ月前、3か月前に次の通知タイミングをセットするコード 省略 } } const car_inspection = new CarInspection( '[email protected]', '2024-11-03', '2024-08-04') if(car_inspection.isNotifyTiming()){ car_inspection.notify() } export interface Notify { notify(): void } export abstract class Maintenance implements Notify{ protected email : string protected expire_date: string protected notify_timing: string constructor(email: string, expire_date: string, notify_timing: string) { this.email = email this.expire_date = expire_date this.notify_timing = notify_timing } notify(): void { this.sendEmail(this.email, '通知処理') this.setNextNotifyTiming() } isNotifyTiming(): boolean { const currentDate = this.formatDate(new Date()); return currentDate == this.notify_timing } abstract setNextNotifyTiming(): void // sendEmail(),calculateDateBefore(),formatDate()は省略 } export abstract class Inspection extends Maintenance { abstract inspect(): boolean } index.ts (エントリーポイント)
  40. 50

  41. 54