0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

デザインパターン勉強会②Adapter

Posted at

はじめに

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を出力するメソッドを持つだけのクラスです。
Adaptee.vb
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役です。
Client.vb
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インタフェースの形に適合します。
ICommand.vb
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`に適合しています。
AdapterInherits
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`に適合しています。
AdapterDelegation
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()を呼ぶことができます。

Client.vb
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クラスには、一切変更を加えず再利用を行うことができました。
既存コードに修正がないことは、以下のメリットがあります。

  • 今回の改修に伴う修正箇所が新規クラスのみに特定される
  • テスト済みの既存コードを安全に再利用することができる
以上のような既存コードの再利用方法が、Adapterパターンの説明になります。

継承と委譲の違い

今回実装した各方法をクラス図にしました。
また、それぞれの違いについて考えてみました。

継承を用いる方法

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1585226/bef135b0-ec68-d39c-2d71-b568649fb40d.png)

継承を用いる方法は、以下の4つの特徴があるという意見が出ました。

  1. AdapterはAdapteeを継承しており多重継承はできないため、Target役はインタフェースでなければならない。
  2. 継承を使う前提なのでAdaptee役の部分をInterfaceとすることはできず、あるInterfaceを実装する具象クラスを適合する場合は、具象クラスの数だけAdapterが必要になる。
  3. 既存コードの実装がまるごとついてきてしまう。今回の場合、再利用したいメソッドはWriteHelloWorld()のみだが、具象クラスをそのままインスタンス化すると外からWriteHoge()を使用することができる。
  4. protectedメソッドまで再利用できる。

委譲を用いる方法

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1585226/4473b697-a4cc-04fd-f685-f6e7cea04e30.png)

一方、委譲を用いる方法には、以下の4つの特徴があるという意見が出ました。

  1. Target役を抽象クラスやクラスとすることができる。
  2. Adaptee役をInterfaceとすることができる。その場合コンストラクタなどの何らかの部分で具象クラスを注入するようにすれば、1つのクラスだけで各具象クラスのAdapterを実現できる。
  3. 既存コードの実装がついてこない。必要なメソッドのみ選択して使うようにできる。
  4. publicメソッドしか再利用できない。

まとめ

今回はAdapterパターンについて学びました。 ラッパを作っているというようなイメージで、理解しやすいパターンでした。 今までは修正があったら既存コードに直接手を加えていたので、今後はApdaterパターンも検討していけたらと思います。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?