Friday, October 15, 2010

 

Thoughts about (object) cloning

One commonly-useful method for objects to support is cloning. Unfortunately, cloning tends to pose many difficulties in object-oriented languages, since the type of object returned by the clone method will vary depending upon the type of object performing it, and since base types may be used to derive some types that allow cloning and some that do not.

As I see it, there are four types of classes when it comes to cloning:
  1. Those that simply cannot be cloned
  2. Those that are perfectly happy if MemberwiseClone'd, but don't promise that their descendant classes will support cloning.
  3. Those that can be meaningfully cloned, but they require more than MemberwiseClone, and they don't promise that their descendants will support cloning.
  4. Those that can be cloned, and promise such ability on behalf of themselves and any descendant classes.

Unfortunately, the .net framework really doesn't do much to indicate which types fall in which category. My proposed pattern would make things much clearer, and would look something like this:

Class SampleSemicloneableBase
Enum DontTryThis
NoReallyDont = 0
End Enum

Public SampleArray1(0) As Integer

Protected Overridable Function CloneBase() As Object
Dim Result As SampleSemicloneableBase
Result = CType(MyBase.MemberwiseClone(), SampleSemicloneableBase)
Result.SampleArray1 = Result.SampleArray1.Clone
Return Result
End Function

Protected Shadows WriteOnly Property MemberwiseClone As DontTryThis
Set(ByVal value As DontTryThis)
Throw New NotSupportedException()
End Set
End Property
End Class

Class CloneableDerived
Inherits SampleSemicloneableBase
Public SampleArray2(0) As Integer

Public Shadows Function Clone() As CloneableDerived
Return CType(Me.CloneBase, CloneableDerived)
End Function

Protected Overrides Function CloneBase() As Object
Dim Result As CloneableDerived
Result = CType(MyBase.CloneBase(), CloneableDerived)
Result.SampleArray2 = Result.SampleArray2.Clone
Return Result
End Function

Public Overrides Function ToString() As String
Return SampleArray1(0).ToString & "." & SampleArray2(0).ToString
End Function
End Class

Class NonCloneableDerived
Inherits SampleSemicloneableBase
Protected Shadows WriteOnly Property CloneBase As DontTryThis
Set(ByVal value As DontTryThis)
Throw New NotSupportedException()
End Set
End Property
End Class

Class cloningTest
Shared Sub test()
Dim thing1 As New CloneableDerived
thing1.SampleArray1(0) = 1
thing1.SampleArray2(0) = 2
Dim thing2 As CloneableDerived = thing1.Clone
Debug.Print(thing1.ToString & "/" & thing2.ToString)
thing2.SampleArray1(0) = 3
thing2.SampleArray2(0) = 4
Debug.Print(thing1.ToString & "/" & thing2.ToString)
End Sub
End Class

For this example, the base class has one field which needs to be cloned, and the cloneable derived class adds another. Each cloneable derived class should declare a "Public Shadows Function Clone()" of its own type, which should call the overridable "CloneBase()" and typecast the result as appropriate. Each cloneable class which needs to do anything when cloned other than call its parent's cloning method should add an override to CloneBase, and that override should start by calling it's parent's CloneBase method.

Classes which aren't going to support cloning-related methods of their parents (e.g. MemberwiseClone in the case of SampleSemicloneableBase, or CloneBase in the case of NonCloneableDerived) should seal off those methods from protected classes; the implementation here is perhaps a little cutesy, but should be pretty good (Intellisense should set people straight if they try it).

Classes which follow my pattern can be readily classified into types #1, #3, and #4. Type #2 is a bit dodgy; classes which follow my pattern would not be type #2 (they'd be #3 even if all they do is call Object.MemberwiseClone) but there's no way of knowing whether a type which supports MemberwiseClone really supports it, or simply hasn't disabled it. But for types which follow the pattern, the cloning status would be readily apparent.

This page is powered by Blogger. Isn't yours?