Často je nutné vytvořit kopii hodnota v Ruby. I když se to může zdát jednoduché, a to je pro jednoduché objekty, jakmile budete muset vytvořit kopii dat struktura s více maticemi nebo hashami na stejném objektu, rychle zjistíte, že jich je mnoho nástrahy.
Předměty a odkazy
Abychom pochopili, co se děje, pojďme se podívat na nějaký jednoduchý kód. Nejprve operátor přiřazení používající typ POD (Plain Old Data) v Rubín.
a = 1
b = a
a + = 1
staví b
Operátor přiřazení zde vytváří kopii hodnoty A a přiřadit jej b pomocí operátora přiřazení. Jakékoli změny A se neodrazí b. Ale co něco složitějšího? Zvaž toto.
a = [1,2]
b = a
a << 3
staví b.inspect
Před spuštěním výše uvedeného programu se pokuste uhodnout, jaký bude výstup a proč. To není stejné jako v předchozím příkladu, provedené změny A se odrážejí v b, ale proč? Je to proto, že Pole objekt není typu POD. Operátor přiřazení nevytváří kopii hodnoty, pouze kopíruje odkaz k objektu Array. A a b proměnné jsou nyní Reference ke stejnému objektu Array budou všechny změny v obou proměnných vidět v druhé.
A nyní můžete vidět, proč může být kopírování netriviálních objektů s odkazy na jiné objekty obtížné. Pokud jednoduše vytvoříte kopii objektu, kopírujete pouze odkazy na hlubší objekty, takže se vaše kopie označuje jako „mělká kopie“.
Co Ruby poskytuje: duplikujte a klonujte
Ruby poskytuje dvě metody pro vytváření kopií objektů, včetně jedné, která může být vytvořena pro vytváření hlubokých kopií. Object # dup metoda vytvoří mělkou kopii objektu. K dosažení tohoto cíle dup metoda bude volat initialize_copy metoda této třídy. To, co přesně dělá, závisí na třídě. V některých třídách, jako je Array, inicializuje nové pole se stejnými členy jako původní pole. Toto však není hluboká kopie. Zvažte následující.
a = [1,2]
b = a.dup
a << 3
staví b.inspect
a = [[1,2]]
b = a.dup
a [0] << 3
staví b.inspect
Co se tady stalo? Pole # initialize_copy metoda skutečně vytvoří kopii pole, ale tato kopie je sama o sobě mělkou kopií. Pokud máte ve svém poli nějaké jiné typy než POD, použijte dup bude pouze částečně hluboká kopie. Bude to jen tak hluboké jako první pole, hlubší pole, hashe nebo jiné objekty budou zkopírovány pouze mělce.
Je třeba zmínit ještě jednu metodu, klonovat. Klonovací metoda dělá to samé jako dup s jedním důležitým rozlišením: očekává se, že objekty přepíšou tuto metodu s metodou, která dokáže provádět hluboké kopie.
Co to tedy v praxi znamená? To znamená, že každá z vašich tříd může definovat klonovací metodu, která vytvoří hlubokou kopii tohoto objektu. To také znamená, že musíte napsat metodu klonování pro každou třídu, kterou vytvoříte.
Trik: Marshalling
Dalším způsobem, jak říci „serializovat“ objekt, je „marshalling“. Jinými slovy, proměňte tento objekt na znakový tok, který lze zapsat do souboru, který můžete později „unmarshal“ nebo „unserialize“ získat a získat stejný objekt. To lze využít k získání hluboké kopie libovolného objektu.
a = [[1,2]]
b = Marshal.load (Marshal.dump (a))
a [0] << 3
staví b.inspect
Co se tady stalo? Marshal.dump vytvoří "výpis" vnořeného pole uloženého v A. Tento výpis je řetězec binárních znaků určený k uložení do souboru. Obsahuje celý obsah pole, úplnou hlubokou kopii. Další, Marshal.load dělá pravý opak. Analyzuje toto pole binárních znaků a vytváří zcela nové pole se zcela novými prvky pole.
Ale to je trik. Je to neefektivní, nebude fungovat na všech objektech (co se stane, pokud se pokusíte klonovat síťové připojení tímto způsobem?) A pravděpodobně to nebude strašně rychlé. Je to však nejjednodušší způsob, jak zkrátit vlastní kopie initialize_copy nebo klonovat metody. Totéž lze udělat s metodami jako to_yaml nebo to_xml pokud máte načteny knihovny, které je podporují.