はじめに
ZOOM勉強会の議事録です。 「増補改訂版Java言語で学ぶデザインパターン入門」を読んで、プログラム作成・パターンの理解を行います。 第6回はPrototypeパターンです。Prototype
インスタンスの複製を作るパターンです。役割は以下です。
- Prototype インスタンスを複製するためのメソッドを定める
- ConcretePrototype インスタンスを複製するメソッドを実際に実装するクラス
- Client インスタンスを複製するメソッドを使用して新しいインスタンスを作成する
今回は.NET Frameworkに用意されているICloneableインタフェースを使用します。
そのため、クラス図は以下のようになります。
ICloneable
複製用のメソッドとして`Clone()`のみが定義されているインタフェースです。 Public Interface ICloneable
Function Clone() As Object
End Interface
あとから知りましたが、ディープコピーとシャローコピーの区別ができないため、非推奨のようです。(ICloneableインタフェース)
また、More Effective C#にもICloneableは、設計の選択肢を狭めるので避けようと書かれています。
今回は、勉強会の議事録なのでICloneableで実装したものを紹介します。
またJavaとVB.NETで仕様が異なります。
JavaのCloneableインタフェースは、マーカーインタフェースと呼ばれる、メソッドを一つも定義していないインタフェースのようです。
ObjectクラスがClone()
を定義しており、通常はCloneNotSupportedExceptionの例外を吐くようになっていますが、Cloneableインタフェースを実装したクラスだけ、例外を吐かずにClone()
を呼べるようになっています。
このClone()
をオーバーライドすることでPrototypeパターンを実現するようになっており、今回紹介する方法とは異なります。
Prototype
複製を行いたいインタフェースにICloneableを継承させます。
Public Interface Shape : Inherits ICloneable
Function GetX() As Integer
Function GetY() As Integer
Sub SetX(ByVal x As Integer)
Sub SetY(ByVal y As Integer)
End Interface
ConcretePrototype
Prototype役を実装した具象クラスを作成します。 `Clone()`メソッドで、複製を実装します。 コピー用のインスタンスを生成し、各フィールドを設定します。 参照型のフィールドをディープコピーしたい場合は、そのクラスの`Clone()`を呼び、再帰的に複製を行います。Public Class Rectangle : Implements Shape
Private x As Integer
Private y As Integer
Private width As Integer
Private height As Integer
Public Sub New(ByVal x As Integer, ByVal y As Integer, ByVal width As Integer, ByVal height As Integer)
Me.x = x
Me.y = y
Me.width = width
Me.height = height
End Sub
Public Sub SetX(x As Integer) Implements Shape.SetX
Me.x = x
End Sub
Public Sub SetY(y As Integer) Implements Shape.SetY
Me.y = y
End Sub
Public Function GetX() As Integer Implements Shape.GetX
Return Me.x
End Function
Public Function GetY() As Integer Implements Shape.GetY
Return Me.y
End Function
Public Function Clone() As Object Implements ICloneable.Clone
Dim cloneObj = New Rectangle(Me.x, Me.y, Me.width, Me.height)
Return cloneObj
End Function
End Class
Client
クローンを使用する側です。 `Clone()`を呼ぶことで複製を作ることができました。 Sub Main()
Dim r1 As Shape = New Rectangle(1, 2, 3, 4)
Dim r2 As Shape = r1.Clone()
r1.SetX(2)
Console.WriteLine(r2.GetX()) ' 1
Console.WriteLine(r1.GetX()) ' 2
End Sub
ICloneableによるPrototypeパターン
メリット
- データを複製するだけなので1から作るより簡単である場合がある
- 外からではコピーできないprivateフィールドもコピーできる
- 呼び出し側はクローンメソッドを呼ぶだけで複製を得られる
デメリット
- ディープコピーされるのかシャローコピーされるのかわからない
- すべての参照型のフィールド変数と派生クラスがICloneableインタフェースを実装していないといけなくなり、階層構造全体がICloneableを実装する必要がある
余談
More Effective C#では、すべてのクラスにICloneableを実装する代わりにClone用のprotectedコンストラクタを使用する方法が紹介されていました。
この方法は、先程のデメリットの「階層構造全体がICloneableを実装する必要がある」という点を回避する手段になっています。
基底クラスは、以下のように派生クラスが複製に使用するprotectedコンストラクタを実装します。
Public Class BaseType
Private label As String
Private values As Integer()
Protected Sub New()
label = "class name"
ReDim values(10)
End Sub
'派生クラスが複製に使用
Protected Sub New(ByVal right As BaseType)
label = right.label
values = CType(right.values.Clone(), Integer())
End Sub
End Class
コピー用のコンストラクタは、引数に自クラスを受け取って、フィールドをコピーします。
また、派生クラスで複製のために使用することを目的としているので、アクセス修飾子をprotectedにしています。
このようにすることで、すべての派生クラスにICloneableの実装を強制せず基底クラス部分の複製を返すことができます。
以下が複製をサポートする場合の派生クラスです。
Public NotInheritable Class Derived : Inherits BaseType : Implements ICloneable
Private dValues As Double()
Public Sub New()
ReDim dValues(10)
End Sub
'基底クラスの複製用コンストラクタを使用して値をコピー
Private Sub New(ByVal right As Derived)
MyBase.New(right)
dValues = CType(right.dValues.Clone(), Double())
End Sub
Public Function Clone() As Object Implements ICloneable.Clone
Dim rVal = New Derived(Me)
Return rVal
End Function
End Class
複製機能を持たせたい派生クラスには、上のようにICloneableを実装します。
ICloneableを実装する派生クラスは、継承されると以降全ての派生クラスとそのフィールドのクラスにICloneableを実装しなければならなくなるので、NotInheritableとします。
そして、こちらも複製用のコンストラクタを用意し、Clone()
で使用します。
このようにすることで、必要なクラスにのみICloneableを実装し、複製機能をもたせることができます。
まとめ
今回はPrototypeパターンについて学びました。 「増補改訂版Java言語で学ぶデザインパターン入門」自体がJavaの本であったため、CloneableとICloneableで使い方が異なりました。 デザインパターンができてから数年経っているため、各パターンの問題点も議論されているようです。 そういった点も考慮して勉強会で議論できるとよりよい勉強になると思いました。また、Javascriptは、プロトタイプベースのオブジェクト指向と言われており、今回勉強したプロトタイプパターンが関わっているようなので、こちらもいつか勉強できたらと思います。