はじめに
ZOOM勉強会の議事録です。 「増補改訂版Java言語で学ぶデザインパターン入門」を読んで、プログラム作成・パターンの理解を行います。 第10回はStrategyパターンです。Strategyパターン
プログラムを切り替え、ポリモーフィズム・多態性を実現する方法です。 役割は以下です。- Strategy ロジックを切り替えるためのインタフェース
- ConcreteStrategy Strategyの具体的な実装。切り替えられるクラス
- Context Starategyを利用する役
本ではじゃんけんプログラムを取り上げていましたが、今回は自作プログラムで説明します。
リファクタリング前
入力された値に応じて返答を返すだけのプログラムです。 また、コマンドライン引数の値に応じて言語が変わります。 Sub Main(ByVal args() As String)
If args.Length = 0 Then
Return
End If
While True
Dim line = Console.ReadLine()
Select Case line
'会ったときの挨拶
Case 1
'コマンドライン引数の値に応じて言語が代わる
If args(0) = 1 Then
Console.WriteLine("こんにちは")
ElseIf args(0) = 2 Then
Console.WriteLine("Hello")
ElseIf args(0) = 3 Then
Console.WriteLine("안녕하세요")
Else
Return
End If
'お礼
Case 2
If args(0) = 1 Then
Console.WriteLine("ありがとう")
ElseIf args(0) = 2 Then
Console.WriteLine("Thank you")
ElseIf args(0) = 3 Then
Console.WriteLine("감사")
Else
Return
End If
'別れの挨拶
Case 3
If args(0) = 1 Then
Console.WriteLine("バイバイ")
ElseIf args(0) = 2 Then
Console.WriteLine("Goodbye")
ElseIf args(0) = 3 Then
Console.WriteLine("안녕")
Else
Return
End If
Case Else
Exit While
End Select
End While
End Sub
Strategy
ロジックを切り替えるためのインタフェースです。 今回は言語のインタフェースを用意しました。Public Interface ILanguage
Sub Hello()
Sub Thanks()
Sub GoodBye()
End Interface
ConcreteStrategy
クラスごとに実装内容を変えています。
例として日本語と英語のみ記載しています。
Public Class Japanese : Implements ILanguage
Public Sub Hello() Implements ILanguage.Hello
Console.WriteLine("こんにちは")
End Sub
Public Sub Thanks() Implements ILanguage.Thanks
Console.WriteLine("ありがとう")
End Sub
Public Sub GoodBye() Implements ILanguage.GoodBye
Console.WriteLine("バイバイ")
End Sub
End Class
Public Class English : Implements ILanguage
Public Sub Hello() Implements ILanguage.Hello
Console.WriteLine("Hello")
End Sub
Public Sub Thanks() Implements ILanguage.Thanks
Console.WriteLine("Thank you")
End Sub
Public Sub GoodBye() Implements ILanguage.GoodBye
Console.WriteLine("Goodbye")
End Sub
End Class
Context
リファクタリング後のMainです。 最初にコマンドライン引数でConcreteStrategy役(言語)が決定されます。 次に入力された値に応じて、`ILanguage`インタフェースのメソッドを介してConcreteStrategy役のメソッドが呼ばれるようになります。 Strategyパターンの導入により、`While`内の分岐がシンプルになったことがわかります。 これは、Strategyパターンによって言語選択の分岐を最初の1つにまとめれるようになったためです。 新しい言語を追加するときも`ILanguage`インタフェースを実装した新しい言語クラスを作り最初の`Select Case`に追加するだけで良くなります。 また、`ILanguage`インタフェースのメソッドがすべて実装されている限り、`While`文は変更する必要もなくなりメソッドの呼び出し側(`While`以降の処理)を使い回すことができます。 Sub Main(ByVal args() As String)
If args.Length = 0 Then
Return
End If
Dim language As ILanguage
'コマンドライン引数に応じて言語が決定
Select Case args(0)
Case 1
language = New Japanese()
Case 2
language = New English()
Case 3
language = New Korean()
Case Else
Return
End Select
While True
Dim line = Console.ReadLine()
'入力された値に応じて表示する言葉を変える
Select Case line
Case 1
language.Hello()
Case 2
language.Thanks()
Case 3
language.GoodBye()
Case Else
Exit While
End Select
End While
End Sub
Strategyパターンの説明は以上です。
余談
メソッド1つのインタフェースとデリゲートの挙動が一緒だと思い、どう使い分けるのがいいのかという話がでました。 例として、二項演算を行うプログラムを作成してみました。Strategy
二項演算を定義しているインタフェースです。Public Interface IBinaryOperator
Function Calculate(ByVal leftOpearnd As Double, ByVal rightOperand As Double) As Nullable(Of Double)
End Interface
ConcreteStrategy
Plus
クラスとMinus
クラスはIBinaryOperator
インタフェースを実装しており、それぞれのCalculate()
は、引数を足し算した結果、引き算した結果を返します。省略しますが、掛け算・割り算なども実装します。
Public Class Plus : Implements IBinaryOperator
Public Function Calculate(leftOpearnd As Double, rightOperand As Double) As Nullable(Of Double) Implements IBinaryOperator.Calculate
Return leftOpearnd + rightOperand
End Function
End Class
Public Class Minus : Implements IBinaryOperator
Public Function Calculate(leftOpearnd As Double, rightOperand As Double) As Nullable(Of Double) Implements IBinaryOperator.Calculate
Return leftOpearnd - rightOperand
End Function
End Class
Factory
以下がInterfaceを使った場合のFactoryです。 演算子を表す文字列を受け取るとそれに応じて`IBinaryOperator`インタフェースを実装した具象クラスを返します。Public Class OperatorFactory
Public Shared Function Create(ByVal operatorStr As String) As IBinaryOperator
Select Case operatorStr
Case "+"
Return New Plus()
Case "-"
Return New Minus()
Case "*"
Return New Multiply()
Case "/"
Return New Divide()
Case Else
Return New NullOperator()
End Select
End Function
End Class
以下はデリゲートの定義とデリゲートを返すメソッドを定義したModuleです。
今回はVB.NETで定義済みのFunc
を使用します。
こちらもCalculate()
と同様にDouble
型2つを受け取ってNullable(Of Double)
型を返すようにしています。
Module DelegateFactory
Public Function Create(ByVal operatorStr As String) As Func(Of Double, Double, Nullable(Of Double))
Select Case operatorStr
Case "+"
Return AddressOf Plus
Case "-"
Return AddressOf Minus
Case "*"
Return AddressOf Multiply
Case "/"
Return AddressOf Divide
Case Else
Return AddressOf NullOperator
End Select
End Function
Private Function Plus(ByVal leftOperator As Double, ByVal rightOperator As Double) As Nullable(Of Double)
Return leftOperator + rightOperator
End Function
Private Function Minus(ByVal leftOperator As Double, ByVal rightOperator As Double) As Nullable(Of Double)
Return leftOperator - rightOperator
End Function
Private Function Multiply(ByVal leftOperator As Double, ByVal rightOperator As Double) As Nullable(Of Double)
Return leftOperator * rightOperator
End Function
Private Function Divide(ByVal leftOperator As Double, ByVal rightOperator As Double) As Nullable(Of Double)
Return leftOperator / rightOperator
End Function
Private Function NullOperator(ByVal leftOperator As Double, ByVal rightOperator As Double) As Nullable(Of Double)
Return Nothing
End Function
End Module
Context
入力チェック等の部分も作成していましたが省略しています。 インタフェースとデリゲートを使うもので同じ結果が出力されます。Module Module1
Sub Main()
Console.Write("受付中 > ")
Dim expressions = Console.ReadLine()
Dim expressionArray = expressions.Split(" ")
Dim leftOperand = expressionArray(0)
Dim operatorStr = expressionArray(1)
Dim rightOperand = expressionArray(2)
Dim result As Nullable(Of Double) = UseInterface(leftOperand, operatorStr, rightOperand)
Dim result2 As Nullable(Of Double) = UseDelegate(leftOperand, operatorStr, rightOperand)
Console.WriteLine("計算結果:" & result)
Console.WriteLine("計算結果:" & result2)
Console.ReadLine()
End Sub
Public Function UseInterface(ByVal leftOperand As Double, ByVal operatorStr As String, ByVal rightOperand As Double) As Nullable(Of Double)
Dim operator1 = OperatorFactory.Create(operatorStr)
Return operator1.Calculate(leftOperand, rightOperand)
End Function
Public Function UseDelegate(ByVal leftOperand As Double, ByVal operatorStr As String, ByVal rightOperand As Double) As Nullable(Of Double)
Dim operatorFunction = DelegateFactory.Create(operatorStr)
Return operatorFunction(leftOperand, rightOperand)
End Function
End Module
メソッド1つのインタフェースとデリゲートを使用する2つの方法を紹介しました。
どちらを使うべきかはプロジェクトに合わせて考える必要があるという結論になりました。
今回は簡単な数学の二項演算をする関数を定義したかったものになります。そのような意図を持った抽象化を行うときはインタフェースが良いと考えました。
理由として、インタフェースはより明確に何を抽象化しているかを表現できると考えたからです。
今回のDelegateFactory
の中には、Func(Of Double, Double, Nullable(Of Double))
な形の関数を返すことのみが定義されており、それが簡単な二項演算を返すことを表現できていません。
一方、インタフェースはIBinaryOperator
という名前によって、二項演算子による計算を行っていることを表現できます。