0
0

More than 3 years have passed since last update.

デザインパターン勉強会⑥Prototype

Posted at

はじめに

ZOOM勉強会の議事録です。
「増補改訂版Java言語で学ぶデザインパターン入門」を読んで、プログラム作成・パターンの理解を行います。
第6回はPrototypeパターンです。

Prototype

インスタンスの複製を作るパターンです。

役割は以下です。

  • Prototype
  • インスタンスを複製するためのメソッドを定める
  • ConcretePrototype
  • インスタンスを複製するメソッドを実際に実装するクラス
  • Client
  • インスタンスを複製するメソッドを使用して新しいインスタンスを作成する

今回は.NET Frameworkに用意されているICloneableインタフェースを使用します。
そのため、クラス図は以下のようになります。

image.png

ICloneable

複製用のメソッドとしてClone()のみが定義されているインタフェースです。

ICloneable
    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を継承させます。
Prototype

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()を呼び、再帰的に複製を行います。

ConcretePrototype
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()を呼ぶことで複製を作ることができました。

Client
    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コンストラクタを実装します。

BaseType
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の実装を強制せず基底クラス部分の複製を返すことができます。

以下が複製をサポートする場合の派生クラスです。

Derived
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は、プロトタイプベースのオブジェクト指向と言われており、今回勉強したプロトタイプパターンが関わっているようなので、こちらもいつか勉強できたらと思います。

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