Slide 1

Slide 1 text

オブジェクト指向と Dependency Injection 加納 誠人

Slide 2

Slide 2 text

アジェンダ 1. オブジェクトのライフタイム 2. オブジェクト指向の三原則 3. Dependency Injection

Slide 3

Slide 3 text

オブジェクトのライフタイム

Slide 4

Slide 4 text

オブジェクトのライフタイム 1. メモリに展開する 2. 初期化(コンストラクターが呼び出される) 3. プロパティやメソッドを使用する 4. 参照が外れる 5. ガベージコレクションが片付ける メモリ展開 初期化 使用 参照が外れる GCによる回収

Slide 5

Slide 5 text

オブジェクト指向の三原則

Slide 6

Slide 6 text

オブジェクト指向の三原則 1. カプセル化 2. 継承 3. ポリモーフィズム(多態性・多様性)

Slide 7

Slide 7 text

カプセル化

Slide 8

Slide 8 text

カプセル化とは? カプセル化とは、データとメソッドをひとつのオブジェクトにまとめ、 その内容を隠蔽することを指す プロパティでフィールド(メンバ変数)を隠蔽するだけではない オブジェクトの内部と外部の境界線を意識することが重要

Slide 9

Slide 9 text

アクセス修飾子(※1) 名称 可視・不可視 可変・不変 Private 不可視 (不可視の為、不変) Public ReadOnly(※2) 可視 不変 Public 可視 可変 ※1 Internal、Protected および Protected Internal は割愛 ※2 フィールド(メンバ変数) および プロパティのみ

Slide 10

Slide 10 text

可視・不可視の境界線 ● 外側から見た時の見た目(⇔ わかりやすさ) ● 手触り(⇔ 使いやすさ) 「わかりやすい」もの・「使いやすい」ものを作る=クラス設計 設計というと固い印象を受けるけど、英訳すると Design になる よい見た目と手触りをデザインしよう

Slide 11

Slide 11 text

メソッドの場合 可視のメソッド=そういうことができる その集合体=クラス ふるまい (Behavior)

Slide 12

Slide 12 text

ふるまいをハッキリさせると・・・ ● クラスの役割がハッキリする ● ライフタイムが適切に終わりやすい(⇔便利屋クラスは長生き) ※ 専門用語としては、凝集度(強度)が高いというけれど、正直わかりづらい

Slide 13

Slide 13 text

ふるまいをシンプルにすると・・・ ● 個々の関係性がシンプルになる(⇔便利屋は得意先が多い) ● ライフタイムが別れる ● クラスの数は増える(やり過ぎ注意!) ※ 専門用語としては、結合度が低いというけれど、正直わかりづらい

Slide 14

Slide 14 text

Public Class MailSender #Region “Methods” Public Sub Send(message As String) Dim mime = CreateMimeMessage(message) client.Send(mime) End #End Region Non-Public Methods End Class

Slide 15

Slide 15 text

Public Class FileCreator #Region “Methods” Public Function Create(filePath As String) As FileInfo CreateFile(filePath) Return New FileInfo(filePath) End #End Region Non-Public Methods End Class

Slide 16

Slide 16 text

可変・不変の境界線 オブジェクトの保持するデータが頻繁に変わることは、 予期せぬ組み合わせを作りやすい(=バグを埋め込みやすい) データを変える頻度を減らすと・・・ オブジェクトのライフタイムが短くなる ● Setアクセサのみ不可視(Private)にする ● 不変オブジェクト

Slide 17

Slide 17 text

Setアクセサのみ不可視にする Setアクセサに「Private」を付加することで、 内部からのみデータを変更することが可能になる 外部からは読み取り専用と同等になる デメリットは、自動プロパティでは定義できないこと

Slide 18

Slide 18 text

Public Class Voucher #Region “Properties” Public Property Id As Integer Get Return _id End Get Private Set(value As Integer) _id = value End Set End Property #End Region End Class

Slide 19

Slide 19 text

不変オブジェクト インスタンスを生成し終わった後は、 保持するデータが一切変更できないオブジェクト 読み取り専用(ReadOnly)のプロパティのみを用意して、 コンストラクターの引数を設定する (VS2015 より読み取り専用の自動プロパティに設定可能になった)

Slide 20

Slide 20 text

Public Class Voucher #Region “Constructors” Public Sub New(id As Integer) Id = id ‘ ReadOnlyの為、コンストラクターでのみ設定可能 End #End Region #Region “Properties” Public ReadOnly Property Id As Integer #End Region End Class

Slide 21

Slide 21 text

オブジェクトをただの入れ物にしない ● データの保持(フィールドまたはプロパティ) ● 保持しているデータを利用して操作する(メソッド) データの保持を目的としたクラスに 操作を持たせるイメージが沸かない場合・・・ ToStringメソッドを継承して、デバッグに必要な情報を返す

Slide 22

Slide 22 text

Public Class Voucher #Region “Properties” Public ReadOnly Property Id As Integer #End Region #Region “Methods” Public Overrides Function ToString() As String Return $”{NameOf(Id)} = {Id}” ‘ VS2015で挿入文字列およびNameOf演算子に対応 End Function #End Region End Class

Slide 23

Slide 23 text

まとめ ● オブジェクトの内部と外部の境界線を意識して設計する ● ふるまいをハッキリと、シンプルにする ● 外部から変更できないデータを明確にする ● オブジェクトにデータとメソッド両方を持たせる

Slide 24

Slide 24 text

継承

Slide 25

Slide 25 text

継承とは? あるオブジェクトが他のオブジェクトの特性を引き継ぐことを指す 「オブジェクト指向 継承」で検索すると・・・ ● オブジェクト指向 継承 デメリット ● オブジェクト指向 継承 不要 サジェストで上記の様に候補が出る

Slide 26

Slide 26 text

Public Class Child Inherits Parent #Region “Methods” Protected Overrides Sub ExecuteImpl() MyBase.ExecuteImpl() (省略) End Function #End Region End Class

Slide 27

Slide 27 text

継承のデメリット ● コードを追跡しづらい ● 扱うアクセス修飾子が増える (Protected・Protected Internal) ● 親クラスを変更した場合に全ての子クラスに影響が及ぶ

Slide 28

Slide 28 text

コードが追跡しづらい(1/2) 親クラスと子クラスを順に追わないといけない Public メソッド Protected メソッド 呼び出し元

Slide 29

Slide 29 text

コードが追跡しづらい(2/2) 親子共に Public なメソッドが定義できてしまう Public メソッド Public メソッド 呼び出し元

Slide 30

Slide 30 text

親クラスを変更した場合 子クラスAの変更により親クラスを変更した場合、 関係のない子クラスBに影響を及ぼしてしまう 親クラス 子クラスA 子クラスB 影響 影響

Slide 31

Slide 31 text

継承に代わって・・・ 継承の代わりに委譲という手法を使う ● 親子は役割が同じだが、委譲は役割が異なる(一部を任せる) ● 扱うアクセス修飾子は通常と同じ(Protected・Protected Internalは使用しない) ● 変更した場合に委譲先の差し替えや委譲先の追加で対応しやすい

Slide 32

Slide 32 text

委譲

Slide 33

Slide 33 text

委譲とは? 委譲とは、あるオブジェクトの操作を一部他のオブジェクトに代替させる手法のこと

Slide 34

Slide 34 text

Public Class MailDeliveryService #Region “Methods” Public Sub Deliver() (ローカル変数 messageの作成) ‘ 添付ファイルを作成 Dim attachment = _attachmentCreator.Create(filePath) _mailSender.Send(message, attachment) End Function #End Region End Class

Slide 35

Slide 35 text

#Region “Fields” Private ReadOnly _attachmentCreator As IFileCreator Private ReadOnly _mailSender As IMailSender #End Region #Region “Constructors” Public Sub New(attachmentCreator As IFileCreator mailSender As IMailSender) _attachmentCreator = attachmentCreator _mailSender = mailSender End Sub #End Region

Slide 36

Slide 36 text

インターフェイス

Slide 37

Slide 37 text

インターフェイスとは? アクセス修飾子が Public になるメソッドとプロパティのみを定義する Public のメソッドを定義すること=ふるまいを定義すること

Slide 38

Slide 38 text

Public Interface IFileCreator #Region “Properties” ReadOnly Property FileType As FileTypeDivision #End Region #Region “Methods” Function Create(filePath As String) As FileInfo #End Region End Class

Slide 39

Slide 39 text

Public Class ExcelFileCreator Implements IFileCreator #Region “Properties” Public ReadOnly Property FileType As FileTypeDivision Implements IFileCreator.FileType = FileTypeDivision.Excel #End Region #Region “Methods” Public Function Create(filePath As String) As FileInfo Implements IFileCreator.Create (Excelファイルを作成) End Function #End Region End Class

Slide 40

Slide 40 text

Strategyパターン GoF によるデザインパターンのひとつ Strategyは「戦略」を意味し、戦略の切り替えや追加を簡単に行える様になる

Slide 41

Slide 41 text

Public Class PdfFileCreator Implements IFileCreator #Region “Properties” Public ReadOnly Property FileType As FileTypeDivision Implements IFileCreator.FileType = FileTypeDivision.Pdf #End Region #Region “Methods” Public Function Create(filePath As String) As FileInfo Implements IFileCreator.Create (PDFファイルを作成) End Function #End Region End Class

Slide 42

Slide 42 text

Public Class MailDeliveryService #Region “Fields” Private ReadOnly _attachmentCreatorDic As IDictionary(Of FileTypeDivision, IFileCreator) = New Dictionary(Of FileTypeDivision, IFileCreator)() #End Region #Region “Methods” Public Sub Deliver() Dim fileType = order.FileType Dim attachment = _attachmentCreatorDic(fileType).Create(filePath) End Function #End Region End Class

Slide 43

Slide 43 text

単体テストのスタブを作成する 委譲元の単体テストを実施する際、 委譲先はテスト対象ではない為、ダミー(=スタブ)を適用する場合がある

Slide 44

Slide 44 text

Public Class StubFileCreator Implements IFileCreator #Region “Properties” Public ReadOnly Property FileType As FileTypeDivision Implements IFileCreator.FileType = FileTypeDivision.None #End Region #Region “Methods” Public Function Create(filePath As String) As FileInfo Implements IFileCreator.Create Return New FileInfo(String.Empty) End Function #End Region End Class

Slide 45

Slide 45 text

まとめ ● 継承の代わりに委譲を使う ● 委譲元はインターフェイスのみを参照する ● インターフェイスのみの参照は切り替えがしやすい ○ 改修による切り替え ○ 判定による切り替え(Strategyパターン) ○ 単体テストのスタブ

Slide 46

Slide 46 text

Dependency Injection

Slide 47

Slide 47 text

Dependency Injectionとは? 日本語訳は「依存性の注入」と訳される コンポーネント間の依存関係をプログラムのソースコードから排除し、 外部の設定ファイルなどで注入できるようにするソフトウェアパターンである

Slide 48

Slide 48 text

シュークリームに例える(1/3) クリームが入っていない=不良品 シュークリームは、クリームが入っていないと成立しない  ⇒ シュークリームはクリームに依存している クリームをオブジェクトに置き換えると、 「オブジェクトを注入する」となる

Slide 49

Slide 49 text

Public Class ChouCream #Region “Fields” Private ReadOnly _cream As ICream #End Region #Region “Constructors” Public Sub New(cream As ICream) _cream = cream End Sub #End Region End Class

Slide 50

Slide 50 text

シュークリームに例える(2/3) クリームは注入器を使って注入する 注入する仕組みを「DI」、 注入器のクリームをため込むタンク部分が「DIコンテナ」となる

Slide 51

Slide 51 text

DIコンテナとは? DIコンテナは、依存関係の集約先を指す DIを利用してオブジェクトを生成する際は、 依存先のオブジェクトをDIコンテナから注入して用意する 本セッションでは、DIコンテナの パッケージ「Unity」を使って説明する

Slide 52

Slide 52 text

#Region “Methods” Public Sub Execute() Dim container = New UnityContainer() container.RegisterType(Of ICream, CustardCream)() ‘ クリームをタンクに詰める Dim shouCream = container.Resolve(Of ShouCream)() ‘ タンクから CustardCream が注入される End Sub #End Region

Slide 53

Slide 53 text

シュークリームに例える(3/3) 注入するクリームはカスタードでもチョコでも抹茶でもよい Strategyパターンを使用して、 注入するクリーム(=オブジェクト)を切り替えられる

Slide 54

Slide 54 text

#Region “Methods” Public Sub Execute() Dim container = New UnityContainer() container.RegisterType(Of ICream, ChocolateCream)(“ChocoCrem”) ‘ 名前を付けて登録する ‘ 作り方を登録する container.RegisterType(Of ShouCream)(“Choco”, New InjectionConstructor(container.Resolve(container.Resolve(Of ICream)(“ChocoCream”)))) Dim shouCream = container.Resolve(Of ShouCream)(“Choco”) End Sub #End Region

Slide 55

Slide 55 text

2通りの注入方法 ● Constructor Injection(コンストラクターへの注入) ● Property Injection(プロパティへの注入) 次の2点から Constructor Injection をオススメする 1. 生成時に注入する=最速で注入する為 2. 注入したオブジェクトを読み取り専用(ReadOnly)にできる為

Slide 56

Slide 56 text

Public Class ChouCream #Region “Fields” Private ReadOnly _cream As ICream #End Region #Region “Constructors” Public Sub New(cream As ICream) _cream = cream End Sub #End Region End Class

Slide 57

Slide 57 text

ServiceLocatorアンチパターン ● 静的メソッドでどこからでもインスタンスを取得できてしまう ● せっかく依存関係を排除したのに、 ServiceLocator に依存してしまう

Slide 58

Slide 58 text

#Region “Methods” Public Sub Register() Dim container = New UnityContainer() container.RegisterType(Of ICream, CustardCream)() Dim service = New UnityServiceLocator(container) ServiceLocator.SetLocatorProvider(Function() service) End Sub Public Sub Execute() Dim shouCream = ServiceLocator.Current.GetInstance(Of ShouCream)() End Sub #End Region

Slide 59

Slide 59 text

まとめ ● DIとは依存関係を集約し、依存しているオブジェクトの生成時に注入する仕組みのこと ● DIコンテナは依存関係の集約先 ● 注入方法は Constructor Injection (コンストラクターへの注入)がオススメ ● ServiceLocator はそれ自体に依存してしまう為、アンチパターンとされている