A prototype tervezési minta lehetÅ‘vé teszi a meglévÅ‘ objektumok másolását anélkül, hogy a kódot más osztályoktól függÅ‘vé tenné. Fejlesztés közben adódhat a probléma, hogy egy-egy meglévÅ‘ objektum másolatára van szükségünk. Érték tÃpusoknál ez megoldott, de referencia tÃpusok esetén ezt nekünk kell valahogy implementálnunk.
Egy megoldás lenne, hogy készÃtünk egy külön komponenst ennek az elvégzésére, de ez nem biztos, hogy működÅ‘képes, mivel a klónozandó objektumunk rendelkezhet privát adattagokkal, amiket kÃvülrÅ‘l nem tudunk elérni. Persze használhatnánk reflectiont, de ez nem minden nyelvben adott (a design patternek meg nyelv függetlenek), illetve ez valószÃnűleg a problémából inkább csak problémákat gyártana.
A megoldás tehát, hogy az objektumunkon elhelyezünk egy Clone metódust, ami egy másolatot tud készÃteni. .NET esetén erre a keretrendszer beépÃtetten tartalmaz egy interfészt, ami az ICloneable nevet viseli. Ez egy object Clone() metódust definiál. Az interfésznek generikus változata nincs, mivel a klónozás témaköre annyira nem egyszerű.
Beszélhetünk shallow copy és deep copy objektumokról. Shallow copy-ról akkor beszélünk, ha a másolat tulajdonságai ugyanazokat a hivatkozásokat tartalmazzák (ugyanazokra a mögöttes értékekre mutatnak), mint az az objektum, amiről a másolat készült. Ezzel szemben a deep copy egy olyan másolat, amelynek a tulajdonságai nem ugyanazokat a hivatkozásokat tartalmazza, mint annak a forrásobjektumnak a tulajdonságai, amelyről a másolat készült.
Utóbbi implementálása nem triviális, mivel ha az objektumunk függőségekkel rendelkezik, akkor a függőségek referenciájit is másolni kell, ami nem biztos, hogy annyira egyszerű. Éppen ezért .NET 2.0 óta minden objektum rendelkezik a MemberwiseClone() metódussal, ami egy shallow copy-t gyárt az aktuális objektumból.
Ha a deep copyt szeretnénk implementálni, akkor azt nekünk kell megtennünk. Ennek elsÅ‘ lépése, hogy deklarálunk egy tÃpusos interfészt, ami definiálja a Clone() metódusunkat:
public interface ICloneable<out T> : ICloneable where T : class
{
T DeepClone();
}
public sealed class Student : ICloneable<Student>
{
public string Name { get; init; }
public int Class { get; init; }
public double Average { get; init; }
public Student()
{
Name = string.Empty;
}
public Student DeepClone()
{
return new Student
{
Name = new string(Name),
Class = Class,
Average = Average
};
}
public object Clone()
{
return MemberwiseClone();
}
}
A bevezetett ICloneable<T> interfész öröklÅ‘dik az ICloneable-bÅ‘l is, aminek a metódusa shallow copy létrehozásra lesz használva, mÃg ahogy a DeepClone() metódus neve mutatja, ez egy deep copy létrehozására fog szolgálni.
A Student osztály esetén a DeepClone implementáció különlegessége a Name tulajdonság másolásában van. A new string() megoldással allokálunk egy másolatot a szövegből. Itt megjegyzem, hogy a .NET string kezelése miatt ez nem biztos, hogy valódi másolat lesz, de ha nem is lesz az, nem fog problémát okozni a string implementációjából adódóan.
Azonban ha más referencia tÃpusokat is tartalmazna az osztályunk, akkor azoknak is implementálniuk kellene az ICloneable<T> interfészt, hogy valódi másolat születhessen. Ha ez nem történne meg és szimplán csak az = operátorral másolnánk, akkor egy shallow copy objektumot kapunk.
Előnyök
- Megszabadulhatunk vele ismételt inicializációs kódoktól, ha elÅ‘re elkészÃtett prototÃpusokat klónozunk
- LeegyszerűsÃti az összetett objektumok előállÃtását
Hátrányok
- Körkörös referenciával rendelkező objektumok klónozása nem egyszerű
Record
A C# 9-ben és 10-ben tovább tökéletesÃtett record módosÃtó az osztályunkat felruházza egy fordÃtó által generált prototype implementációval is. Ez a prototype implementáció a $<Clone> nevet kapja IL szinten. C# szinten közvetlenül nem tudjuk meghÃvni, de ez a metódus fut le elÅ‘ször, ha a with kulcsszóval manipuláljuk a másolat objektumunkat.