はじめに
ZOOM勉強会の議事録です。 第2回は、Adapterパターンです。Adapterとは
既存コードに一皮被せて、対象となる形に適合させるパターンです。 このパターンを使用することによって、既存コードに手を加えず、既存コードの機能を再利用することができます。登場する役割は以下の通りです。
- Adaptee 再利用される既存のコード
- Client Target役を使って処理を行う
- Target Adaptee役を適合させる対象
- Adapter Adaptee役をTarget役に変換する役割(変換アダプタみたいな感じ)
Adapterパターンを役割で説明すると、既存コード(Adaptee役)に対して、Adapter役を被せて対象となるTarget役の形にしClient役で使用します。
今回は、練習として自分たちでプログラムを作成してみました。
https://github.com/tonightoo/AdapterSample
Adaptee
Adaptee役は、Adapterパターンの中で再利用される既存のコードです。 以下のAdaptee役を用意しました。 HelloWorldやHogeを出力するメソッドを持つだけのクラスです。Public Class Adaptee
Public Sub WriteHelloWorld()
Console.WriteLine("HelloWorld")
End Sub
Public Sub WriteHoge()
Console.WriteLine("Hoge")
End Sub
End Class
Client
Adaptee役のコードが再利用される場所がClient役です。 以下が今回のClient役です。Module Client
Public Sub Main(ByVal args As String())
If args.Length = 0 Then
Exit Sub
End If
Dim command As ICommand
Select Case args(0)
Case 1
command = New Command1()
Case 2
command = New Command2()
Case 3
command = New Command3()
Case Else
Exit Sub
End Select
command.StartLog()
command.CommandMain()
command.EndLog()
End Sub
End Module
コマンドライン引数の値に応じて処理を変えるプログラムを用意しました。
今回は数字の4が入力されたときにAdaptee
クラスのWriteHelloWorld()
を実行するよう修正の依頼が来た状況を想定しています。
既存コードはICommand
インタフェースで処理を切り替えているため、Adaptee
クラスにもICommand
インタフェースを実装したくなります。
Target
Adaptee役はTarget役の形に適合します。 今回はClientで使われている以下のICommandインタフェースがTarget役であり、AdapteeクラスがICommandインタフェースの形に適合します。Public Interface ICommand
'開始時のLogを書く
Sub StartLog()
'Mainの処理
Sub CommandMain()
'終了時のLogを書く
Sub EndLog()
End Interface
Adapter
Adapter役です。 Adapter役はTarget役の`ICommand`インタフェースを実装し、その`CommandMain()`で`WriteHelloWorld()`を呼びます。 今回`StartLog()`と`EndLog()`については、`Adaptee`クラスの方に対応するメソッドがないため、新規で実装することになります。先程説明したとおりAdapter役を実現する方法は、
・継承を用いる方法
・委譲を用いる方法
の2つあります。
それぞれに関して説明します。
継承を用いる方法
`Adapter`クラスが`Adaptee`クラスを継承し、Target役の`ICommand`インタフェースを実装します。 `CommandMain()`の中で`Adaptee`クラスの`WriteHelloWorld()`を呼ぶことで`Adaptee`クラスをTarget役の`ICommand`に適合しています。Public Class Adapter : Inherits Adaptee : Implements ICommand
Public Sub StartLog() Implements ICommand.StartLog
Console.WriteLine("処理を開始します!")
End Sub
Public Sub CommandMain() Implements ICommand.CommandMain
Me.WriteHelloWorld()
End Sub
Public Sub EndLog() Implements ICommand.EndLog
Console.WriteLine("処理を終了します!")
End Sub
End Class
委譲を用いる方法
`CommandMain()`の中で`Adaptee`クラスをインスタンス化し、`WriteHelloWorld()`を呼ぶということをしています。 こちらもこの方法で`Adaptee`クラスをTarget役の`ICommand`に適合しています。Public Class Adapter : Implements ICommand
Private adaptee As New Adaptee
Public Sub StartLog() Implements ICommand.StartLog
Console.WriteLine("処理を開始します!")
End Sub
Public Sub CommandMain() Implements ICommand.CommandMain
adaptee.WriteHelloWorld()
End Sub
Public Sub EndLog() Implements ICommand.EndLog
Console.WriteLine("処理を終了します!")
End Sub
End Class
Adapter
クラスを以上のように定義し、Client
クラスを以下のように変更することでCommandMain()
の部分でWriteHelloWorld()
を呼ぶことができます。
Module Client
Public Sub Main(ByVal args As String())
If args.Length = 0 Then
Exit Sub
End If
Dim command As ICommand
Select Case args(0)
Case 1
command = New Command1()
Case 2
command = New Command2()
Case 3
command = New Command3()
'''''''''''↓ここを追加''''''''''''''''''''''''''''''
Case 4
command = New Adapter()
'''''''''''''''''''''''''''''''''''''''''''''''''''
Case Else
Exit Sub
End Select
command.StartLog()
command.CommandMain()
command.EndLog()
End Sub
End Module
Adapterパターンを使用することで、既存コードのAdaptee
クラスには、一切変更を加えず再利用を行うことができました。
既存コードに修正がないことは、以下のメリットがあります。
- 今回の改修に伴う修正箇所が新規クラスのみに特定される
- テスト済みの既存コードを安全に再利用することができる
継承と委譲の違い
今回実装した各方法をクラス図にしました。
また、それぞれの違いについて考えてみました。
継承を用いる方法
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1585226/bef135b0-ec68-d39c-2d71-b568649fb40d.png)継承を用いる方法は、以下の4つの特徴があるという意見が出ました。
- AdapterはAdapteeを継承しており多重継承はできないため、Target役はインタフェースでなければならない。
- 継承を使う前提なのでAdaptee役の部分をInterfaceとすることはできず、あるInterfaceを実装する具象クラスを適合する場合は、具象クラスの数だけAdapterが必要になる。
- 既存コードの実装がまるごとついてきてしまう。今回の場合、再利用したいメソッドはWriteHelloWorld()のみだが、具象クラスをそのままインスタンス化すると外からWriteHoge()を使用することができる。
- protectedメソッドまで再利用できる。
委譲を用いる方法
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1585226/4473b697-a4cc-04fd-f685-f6e7cea04e30.png)一方、委譲を用いる方法には、以下の4つの特徴があるという意見が出ました。
- Target役を抽象クラスやクラスとすることができる。
- Adaptee役をInterfaceとすることができる。その場合コンストラクタなどの何らかの部分で具象クラスを注入するようにすれば、1つのクラスだけで各具象クラスのAdapterを実現できる。
- 既存コードの実装がついてこない。必要なメソッドのみ選択して使うようにできる。
- publicメソッドしか再利用できない。