|
Dit artikel is gepubliceerd op zondag 31 juli 2011 op vbvoorbeelden, bezoek de website voor een recente versie van dit artikel of andere artikels.
Regelmatig komt het voor dat men zou willen werken met kopies ( of ook wel klonen genoemd ) van objecten, waarbij men dan kan werken met de kopie/kloon die initieel dezelfde toestand heeft als het originele object.
Veronderstel onderstaande klasse Division. Visual Basic 2010 Broncode Namespace Example1 Class Division Public Sub New( ByVal dividend As Double, ByVal divisor As Double) Me.Dividend = dividend Me.Divisor = divisor End Sub Private m_Dividend As Double Public Property Dividend() As Double Get Dividend = m_Dividend End Get Set( ByVal value As Double) m_Dividend = value End Set End Property Private m_Divisor As Double Public Property Divisor() As Double Get Divisor = m_Divisor End Get Set( ByVal value As Double) m_Divisor = value End Set End Property Public ReadOnly Property Quotient() As Decimal Get Quotient = Convert.ToDecimal(Dividend / Divisor) End Get End Property Public Overrides Function ToString() As String ToString = Dividend.ToString() & " / " & Divisor.ToString() & _ " = " & Quotient.ToString() End Function End ClassEnd NamespaceDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
Om in een client een kloon te bekomen van een bepaald Division object, kunnen we ( indien de constructor dit toelaat ) de toestand van het te klonen object doorgeven aan de constructoren van de kloon. Visual Basic 2010 Broncode Namespace Example1 Class Client1 Public Shared Sub Main() Dim division1 As Division = New Division(10, 5) Dim division1Clone As Division = _ New Division(division1.Dividend, division1.Divisor) Console.WriteLine(division1.ToString()) Console.WriteLine(division1Clone.ToString()) division1Clone.Dividend = 20 Console.WriteLine(division1.ToString()) Console.WriteLine(division1Clone.ToString()) Console.ReadLine() End Sub End ClassEnd NamespaceDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
Console Application Output 10 / 5 = 2
10 / 5 = 2
10 / 5 = 2
20 / 5 = 4 Deze werkwijze slaagt zolang we over een constructor beschikken die dit toelaat. Vaak is het echter zo dat niet de volledige toestand kan worden geïnjecteerd in een object, dan wordt het moeilijker.
Bemerk dat een wijziging aan de kloon, niets zal wijzigen aan het origineel ( en omgekeerd ).
Het blijft hoe dan ook zo dat de client hier het werk moet verrichten om aan een kloon van het originele object te bekomen.
We kunnen de verantwoordelijkheid voor het kunnen maken van een kloon ook verschuiven naar het te klonen datatype zelf. Dus hier bijvoorbeeld ervoor zorgen dat het type Division zelf een kloon kan opleveren. Division is immers de "information expert" van deze verantwoordelijkheid.
"Information expert" is een pattern dat stelt dat je een verantwoordelijkheid toekent aan dat type dat beschikt over voldoende informatie ( het "information expert" type ) om die verantwoordelijkheid te vervullen. Een Division object beschikt hier over alle informatie ( de Dividend en Divisor ) om een kloon te creëren. 21.6.1. ICloneable InterfaceIn datatypes waarvan klonen van instanties worden gemaakt, implementeert men vaak de ICloneable interface. Dit is niet noodzakelijk, maar het maakt objecten van dat datatype meteen in een ICloneable-context bruikbaar.
Iets wat ICloneable is, beschikt over een Function Clone() As Object method, die in zen implementatie een kloon oplevert van het object waarvan de kloon wordt opgevraagd.
We kunnen de Division klasse als volgt gaan uitbreiden : Visual Basic 2010 Broncode Namespace Example1 Partial Class Division : Implements ICloneable Private Function getClone() As Object Implements System.ICloneable. Clone getClone = New Division(Dividend, Divisor) End Function Public Function Clone() As Division Clone = DirectCast(getClone(), Division) End Function End ClassEnd NamespaceDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
"Private-implementation" werd hier toegepast om te vermijden dat de publieke interface van Division zou beschikken over een niet strongly typed getClone() member. Een kloon van een Division object zal immers nooit een Object, maar altijd een Division zijn. Een strongly typed Clone() method werd hier dan wel aan de publieke interface van Division toegevoegd. Visual Basic 2010 Broncode Namespace Example1 Partial Class Client2 Public Shared Sub Main() Dim division1 As Division = New Division(10, 5) Dim division1Clone As Division = division1.Clone() Console.WriteLine(division1.ToString()) Console.WriteLine(division1Clone.ToString()) division1Clone.Dividend = 20 Console.WriteLine(division1.ToString()) Console.WriteLine(division1Clone.ToString()) Console.ReadLine() End Sub End ClassEnd NamespaceDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
Console Application Output 10 / 5 = 2
10 / 5 = 2
10 / 5 = 2
20 / 5 = 4 Het werk dat de client moet verrichten om een kloon te bekomen is sterk ( en maximaal ) vereenvoudigd.
Tal van voorgedefinieerde types uit de klassenbibliotheek van het .NET Framework zijn reeds ICloneable, enkele voorbeelden : String, Array, ArrayList, SortedList, Queue, Stack, HashTable, ... boven
21.6.2. MemberwiseCloneEen alternatief voor het gebruik van de constructor om een kopie/kloon te creëren, is het gebruik van de Protected Function MemberwiseClone() As Object method die wordt overgeërfd uit de Object klasse (1).
Deze method is ingekapseld ( Protected ) en kan dus niet door clients, maar enkel intern worden gebruikt.
De MemberwiseClone() method zal in Object vorm een kloon opleveren, waarbij de volledige toestand werd gekopieerd. Alle veldwaarden van het originele object worden overgenomen in de kloon. Visual Basic 2010 Broncode Namespace Example2 Class Division : Implements ICloneable Public Sub New( ByVal dividend As Double, ByVal divisor As Double) Me.Dividend = dividend Me.Divisor = divisor End Sub Private m_Dividend As Double Public Property Dividend() As Double Get Dividend = m_Dividend End Get Set( ByVal value As Double) m_Dividend = value End Set End Property Private m_Divisor As Double Public Property Divisor() As Double Get Divisor = m_Divisor End Get Set( ByVal value As Double) m_Divisor = value End Set End Property Public ReadOnly Property Quotient() As Decimal Get Quotient = Convert.ToDecimal(Dividend / Divisor) End Get End Property Public Overrides Function ToString() As String ToString = Dividend.ToString() & " / " & Divisor.ToString() & _ " = " & Quotient.ToString() End Function Private Function getClone() As Object Implements System.ICloneable. Clone getClone = MemberwiseClone() End Function Public Function Clone() As Division Clone = DirectCast(getClone(), Division) End Function End ClassEnd NamespaceDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
In onderstaand client zal de Clone() implementatie een nieuw Divison object creëren en meteen alle velden ( Dividend en Divisor ) initialiseren op een kopie van de veldwaarden van het originele object. Visual Basic 2010 Broncode Namespace Example2 Class Client Public Shared Sub Main() Dim division1 As Division = New Division(10, 5) Dim division1Clone As Division = division1.Clone() Console.WriteLine(division1.ToString()) Console.WriteLine(division1Clone.ToString()) division1Clone.Dividend = 20 Console.WriteLine(division1.ToString()) Console.WriteLine(division1Clone.ToString()) Console.ReadLine() End Sub End ClassEnd NamespaceDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
Console Application Output 10 / 5 = 2
10 / 5 = 2
10 / 5 = 2
20 / 5 = 4 Het gebruik van de MemberwiseClone() method is een ook goed alternatief voor situaties waar de constructor niet zou toelaten alle veldwaarden te injecteren in het nieuwe object.
In onderstaand voorbeeld werd Person ICloneable gemaakt om clients toe te laten klonen te creëren van Person objecten. Visual Basic 2010 Broncode Namespace Example3 Class Address Public Sub New( ByVal street As String) Me.Street = street End Sub Private m_Street As String Public Property Street() As String Get Street = m_Street End Get Set( ByVal value As String) m_Street = value End Set End Property Public Overrides Function ToString() As String ToString = Street End Function End Class Class Person : Implements ICloneable Public Sub New( ByVal name As String, ByVal address As Address) Me.Name = name Me.Address = address End Sub Private m_Name As String Public Property Name() As String Get Name = m_Name End Get Set( ByVal value As String) m_Name = value End Set End Property Private m_Address As Address Public Property Address() As Address Get Address = m_Address End Get Set( ByVal value As Address) m_Address = value End Set End Property Public Overrides Function ToString() As String ToString = Name If Address IsNot Nothing Then ToString &= " - " & Address.ToString() End Function Private Function getClone() As Object Implements System.ICloneable. Clone getClone = MemberwiseClone() End Function Public Function Clone() As Person Clone = DirectCast(getClone(), Person) End Function End Class Class Client1 Public Shared Sub Main() Dim addressPerson1 As Address = New Address( "8th Street") Dim person1 As Person = New Person( "John", addressPerson1) Dim person1Clone As Person person1Clone = person1.Clone() Console.WriteLine(person1.ToString()) Console.WriteLine(person1Clone.ToString()) Console.ReadLine() End Sub End ClassEnd NamespaceDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
Console Application Output John - 8th Street
John - 8th Street Bovenstaande Client lijkt het juist gedrag te vertonen. Maar als we een wijziging zouden aanbrengen aan het adres van person1 of zijn kloon ( person1Clone ) dan zie je dat beide Person objecten hetzelfde Address object delen. Visual Basic 2010 Broncode Namespace Example3 Partial Class Client2 Public Shared Sub Main() Dim addressPerson1 As Address = New Address( "8th Street") Dim person1 As Person = New Person( "John", addressPerson1) Dim person1Clone As Person = person1.Clone() Console.WriteLine(person1.ToString()) Console.WriteLine(person1Clone.ToString()) addressPerson1.Street = "9th Street" Console.WriteLine(person1.ToString()) Console.WriteLine(person1Clone.ToString()) End Sub End ClassEnd NamespaceDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
Console Application Output John - 8th Street
John - 8th Street
John - 9th Street
John - 9th Street De kloon die we de clients toelaten te maken is dus een "shallow clone/copy" ( "ondiepe kloon/kopie" ). Bij het klonen van person1 is een simpele kopie van de referentie van addressPerson1 toegekend aan Address van person1Clone. Beide Person objecten werken dus met hetzelfde Address object.
Dit is een typisch probleem dat geldt voor het maken van klonen van een container die referencetype velden bevat. Bij het maken van een "shallow copy" ( zoals bij het gebruik van de MemberwiseClone() method ) wordt immers steeds slechts de inhoud van de velden gekopieerd. De inhoud van een referencetype variabele is steeds de referentie naar het object. Dat in tegenstelling tot het kopiëren van een valuetype waarde. Elke valuetype variabele/veld is ge-associeerd met een eigen valuetype instantie. Bij het kopiëren van de inhoud van een valuetype variabele/veld naar een andere valuetype variabele/veld zal dus een kopie van de volledige instantie aan de toegekende valuetype variabele/veld worden ge-associeerd. MemberwiseClone() bijvoorbeeld zal dus van valuetype velden wel een "deep clone/copy" ( "diepe kloon/kopie" ) maken. boven
21.6.3. Deep CloneOm toch een "deep clone/copy" van een Person object te kunnen maken, waarbij de kloon van het Person object ook een kloon van het Address object zal bevatten, kunnen we ook het type Address ICloneable maken (1).
Bij het klonen van een Person object moeten dan weer manueel een nieuw Person object creëren, waarbij we aan de constructor van dat nieuw object een kloon van het originele Address object doorgeven (2). Visual Basic 2010 Broncode Namespace Example4 Class Address : Implements ICloneable Public Sub New( ByVal street As String) Me.Street = street End Sub Private m_Street As String Public Property Street() As String Get Street = m_Street End Get Set( ByVal value As String) m_Street = value End Set End Property Public Overrides Function ToString() As String ToString = Street End Function Private Function getClone() As Object Implements System.ICloneable. Clone getClone = MemberwiseClone() End Function Public Function Clone() As Address Clone = DirectCast(getClone(), Address) End Function End Class Class Person : Implements ICloneable Public Sub New( ByVal name As String, ByVal address As Address) Me.Name = name Me.Address = address End Sub Private m_Name As String Public Property Name() As String Get Name = m_Name End Get Set( ByVal value As String) m_Name = value End Set End Property Private m_Address As Address Public Property Address() As Address Get Address = m_Address End Get Set( ByVal value As Address) m_Address = value End Set End Property Public Overrides Function ToString() As String ToString = Name If Address IsNot Nothing Then ToString &= " - " & Address.ToString() End Function Private Function getClone() As Object Implements System.ICloneable. Clone getClone = New Person(Name, Address.Clone()) End Function Public Function Clone() As Person Clone = DirectCast(getClone(), Person) End Function End Class Class Client Public Shared Sub Main() Dim namePerson1 As String = "John" Dim addressPerson1 As Address = New Address( "8th Street") Dim person1 As Person = New Person(namePerson1, addressPerson1) Dim person1Clone As Person = person1.Clone() Console.WriteLine(person1.ToString()) Console.WriteLine(person1Clone.ToString()) person1Clone.Name = "Jane" person1Clone.Address.Street = "9th Street" Console.WriteLine(person1.ToString()) Console.WriteLine(person1Clone.ToString()) Console.ReadLine() End Sub End ClassEnd NamespaceDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
Console Application Output John - 8th Street
John - 8th Street
John - 8th Street
John - 9th Street Bemerk dat we bij het creëren van de "deep clone" van een Person object geen kloon aan de constructor hoeven door te geven van de Name eigenschap. Deze Name eigenschap is nochtans van het type String, wat ook een referencetype is. Het String datatype is "immutable", een wijziging aan de Name van de kloon ( zoals op regel (3) ) zal immers een nieuw String object ( "Jane" ) creëren, en de referentie van dat nieuwe String object toekennen aan de Name eigenschap van de kloon ( person1Clone ). Het originele object person1 zal echter nog altijd dezelfde referentie naar het originele String object ( "John" ) bevatten. We hoeven ons dus niet bezig te houden met het expliciet creëren van klonen van String velden, wat een voordeel is veroorzaakt door de "immutable" karakteristiek van het String datatype.
Zoals je uit vorig voorbeeld kunt merken kan het vervelend zijn een "deep clone/copy" te creëren van een object van een containing type. Zeker indien de contained objecten zelf van een containing type zijn.
Containment wordt zo vaak gebruikt, dat het creëren van een "deep copy" vaak het kopiëren inhoud van een volledige "objectgraph". boven
21.6.4. Klonen via SerialisatieEen eenvoudig alternatief hiervoor is het gebruiken van "serialisatie". Het is hier niet de bedoeling serialisatie te bespreken, maar voor het maken van "deep copies" van volledige "objectgraphs" kan het zo handig zijn dat we dit hier toch nog even gaan illustreren.
We marken het datatype van de objecten die gekloond worden met het Serializable() attribuut. We serialiseren het rootobject naar een memorystream om het vervolgens weer uit deze stream te deserialiseren. En het is bij het deserialiseren dat onze nieuwe objecten ( onze klonen ) worden gemaakt : Visual Basic 2010 Broncode Namespace Example5 <Serializable()> Class Address Public Sub New( ByVal street As String) Me.Street = street End Sub Private m_Street As String Public Property Street() As String Get Street = m_Street End Get Set( ByVal value As String) m_Street = value End Set End Property Public Overrides Function ToString() As String ToString = Street End Function End Class <Serializable()> Class Person : Implements ICloneable Public Sub New( ByVal name As String, ByVal address As Address) Me.Name = name Me.Address = address End Sub Private m_Name As String Public Property Name() As String Get Name = m_Name End Get Set( ByVal value As String) m_Name = value End Set End Property Private m_Address As Address Public Property Address() As Address Get Address = m_Address End Get Set( ByVal value As Address) m_Address = value End Set End Property Public Overrides Function ToString() As String ToString = Name If Address IsNot Nothing Then ToString &= " - " & Address.ToString() End Function Private Function getClone() As Object Implements System.ICloneable. Clone Dim stream As System.IO. MemoryStream = New System.IO. MemoryStream Dim formatter As _ System.Runtime.Serialization.Formatters.Binary.BinaryFormatter = _ New System.Runtime.Serialization.Formatters.Binary. BinaryFormatter formatter.Serialize(stream, Me) stream.Seek(0, System.IO.SeekOrigin.Begin) getClone = formatter.Deserialize(stream) stream.Close() End Function Public Function Clone() As Person Clone = DirectCast(getClone(), Person) End Function End Class Class Client Public Shared Sub Main() Dim namePerson1 As String = "John" Dim addressPerson1 As Address = New Address( "8th Street") Dim person1 As Person = New Person(namePerson1, addressPerson1) Dim person1Clone As Person = person1.Clone() Console.WriteLine(person1.ToString()) Console.WriteLine(person1Clone.ToString()) person1Clone.Name = "Jane" person1Clone.Address.Street = "9th Street" Console.WriteLine(person1.ToString()) Console.WriteLine(person1Clone.ToString()) Console.ReadLine() End Sub End ClassEnd NamespaceDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
Console Application Output John - 8th Street
John - 8th Street
John - 8th Street
John - 9th Street Bemerk dat ondanks het type Address niet ICloneable is, en ondanks dat niet expliciet een kloon wordt gemaakt van het containde Address object, er toch met een kloon/kopie van dat Address object wordt gewerkt in het person1Clone object.
Dit artikel is gepubliceerd op zondag 31 juli 2011 op vbvoorbeelden, bezoek de website voor een recente versie van dit artikel of andere artikels.
|