NAME
Class::Cloneable - A base class for Cloneable objects.
SYNOPSIS
package MyObject;
our @ISA = ('Class::Cloneable');
# calling clone on an instance of MyObject
# will give you full deep-cloning functionality
DESCRIPTION
This module provides a flexible base class for building objects with cloning capabilities. This module does it's best to respect the encapsulation of all other objects, including subclasses of itself. This is intended to be a stricter and more OO-ish option than the more general purpose Clone and Clone::PP modules.
METHODS
Public Method
- clone
-
This provided method will deep copy itself and return the clone, while respecting the encapsulation of any objects contained within itself.
For the most part, this will just "do the right thing" and can be used as-is. If however, you need a more specialized approach, see the section below for details on how you can override and customize this methods functionality.
Inner Package
Class::Cloneable::Util is a protected inner package, meaning that it can only be used by Class::Cloneable or it's subclasses. If an attempt is made to use it outside of that context, an exception is thrown.
This inner package is provided as a means of performing fine grained custom cloning operations for users who choose to or need to override the clone
method provided by Class::Cloneable. Here is a basic example:
package MyMoreComplexObject;
our @ISA = ('Class::Cloneable');
sub clone {
my ($self) = @_;
my $clone = {};
$clone->{dont_clone_this} = $self->{dont_clone_this};
$clone->{clone_this} = Class::Cloneable::Util::clone($self->{clone_this});
return bless $clone, ref($self);
}
NOTE: Many of the functions provided in this package require a $cache
argument, which is a HASH reference that is used internally to keep track of the items already cloned to avoid cloning any circular references more than once. The only function here which does not require a $cache
argument is the clone
function, which will initialize its own $cache
if one is not present.
- clone ($to_clone, $cache)
-
For most custom cloning, just calling this function will be enough. The argument
$to_clone
is required, but the$cache
argument can be omitted. This is only allowed in this function, all other functions in this package require that the$cache
argument is a HASH reference.This function will attempt to clone the
$to_clone
argument following these guidelines:- If
$to_clone
is a Class::Cloneable derived object and$cache
is undefined -
Then it is assumed that you are requested a "root" level cloning and the Class::Cloneable object is deconstructed and its internals are cloned. This results in a deep copy of the object in
$to_clone
, which will recursively call thisClass::Cloneable::Util::clone
function for each internal element. - If
$to_clone
is a Class::Cloneable derived object and$cache
is not undefined -
This is not considered a "root" level cloning so the
clone
method is called on the$to_clone
object. By doing this, we respect the encapsulation of the Class::Cloneable subclass, in case it'sclone
method has been overridden with custom behavior. - If
$to_clone
is an object with an availableclone
method -
This will call the
clone
method found and assume the value returned is a proper clone of the value in$to_clone
. - If
$to_clone
is an object without an availableclone
method -
We assume that because there is no
clone
method available to the object found stored in$to_clone
, that the object does not want to be cloned. The idea is that we should respect the encapsulation of the object and not try to copy it's internals without it's permission. - If
$to_clone
is a reference -
We will deep copy the reference value in
$to_clone
, which will result in recursive calls to theClass::Cloneable::Util::clone
function for all the internal values found inside$to_clone
. This will also properly clone anytied
references, being sure totie
the clones as well.It is important to note that any CODE, RegExp and GLOB references are not copied, only passed through. Short of some really insane perl code, this is pretty much not possible (nor is it really recommended).
- If
$to_clone
is not a reference -
We will simply return a copy of the value in
$to_clone
.
As I said, this function (
clone
) is the central function in this inner package. All other functions are called by this function, and unless you have really specific needs will likely never been used by others. - If
- cloneObject ($to_clone, $cache)
-
This function will assume that
$to_clone
is an object, and try to follow the guidelines above where they pertain to objects. However, if$to_clone
is not an object, and instead an unblessed reference, this function will pass$to_clone
tocloneRef
for cloning.If
$to_clone
is not a reference of some kind and$cache
is not a HASH reference an exception will be thrown. - cloneRef ($to_clone, $cache)
-
This function will deep copy the reference value in
$to_clone
, which will result in recursive calls to theClass::Cloneable::Util::clone
function for all the values found inside$to_clone
.If
$to_clone
is not a reference of some kind and$cache
is not a HASH reference an exception will be thrown.
CAVEATS
This module places no restrictions upon it's subclasses, the user is free to compose their subclasses with just about any type of referent they like; HASH, SCALAR, ARRAY being the most common. However some of the more exotic and rare object construction styles will likely not work as expected (if at all).
For instance, CODE references and closures are incredibly difficult (if not impossible) to clone correctly, objects created from them are not recommended as they are unlikely to work since we cannot accurately clone them. GLOB and RegExp references are also not cloned, and so will not work correctly. However, there is nothing preventing you from just using Class::Cloneable for the sake of interface polymorphism, and implementing your own clone
method with these types of objects.
It is also doubtful that this will work with things like the "Inside-Out" object technique since with this technique object data is not actually stored in the instance, but in a lexical package variable which is keyed by the stringified instance. We can only copy what is found within the actual object instance, and not loosely linked data.
BUGS
None that I am aware of. Of course, if you find a bug, let me know, and I will be sure to fix it.
CAVEATS
This module makes an attempt to handle tied
references, however, the way it approaches them is not ideal and potentially wrong in some cases. So use this with care in such situations.
CODE COVERAGE
I use Devel::Cover to test the code coverage of my tests, below is the Devel::Cover report on this module test suite.
------------------------ ------ ------ ------ ------ ------ ------ ------
File stmt branch cond sub pod time total
------------------------ ------ ------ ------ ------ ------ ------ ------
Class/Cloneable.pm 100.0 100.0 100.0 100.0 100.0 100.0 100.0
------------------------ ------ ------ ------ ------ ------ ------ ------
Total 100.0 100.0 100.0 100.0 100.0 100.0 100.0
------------------------ ------ ------ ------ ------ ------ ------ ------
SEE ALSO
If you need a general purpose cloning module, this is not the module for you, I recommend either Clone (because it's XS and fast) or Clone::PP (if you don't have access to a C compiler). This module only is meant to be used as a base class for objects which need to have cloning abilities, and can not be used outside of that environment.
I want to say first, that this module's code was based heavily on the code found in Clone::PP by Matthew Simon Cavalletto (which in turn was initially derived from Ref.pm by David Muir Sharnoff). Using the code in Clone::PP as a basis for the code in this module saved me a lot of time and I owe a debt to that module and it's author for releasing it Open Source so that this was possible.
Now, you may be wondering why I chose to make yet another cloning module?
My first reason has to do with how these other modules handle objects. Neither Clone and Clone::PP correctly respect object encapsulation. My personal feelings is that if an object does not want to be cloned, it should not be allowed to be cloned (for instance, you would never want to copy a Singleton object, as the whole idea behind a Singleton is that there is only one of them). Upon encountering an object, both Clone and Clone::PP will deep copy it, and therefore violate its encapsulation.
To be fair, if we are talking about really strict encapsulation, then Class::Cloneable violates this too, since it can be used to copy a subclass of Class::Cloneable in a way which technically violates its internal encapsulation. However, this is only for the "root" object, and any subsequent Class::Cloneable objects will be cloned using the clone
method (and therefore respecting encapsulation). So while it is not perfect, I feel it is a good compromise between requiring all classes to compose their own clone
method and allowing arbitrary deep cloning.
The second reason was to provide more flexibility for overriding a clone
method in a subclass. The protected inner package of Class::Cloneable::Util can be used to provide a more fine grained approach to the cloning of your object's internals.
ACKNOWLEDGMENTS
- Thanks for Matthew Simon Cavalletto for writing Clone::PP which this module is based upon.
AUTHOR
stevan little, <stevan@iinteractive.com>
COPYRIGHT AND LICENSE
Copyright 2004-2006 by Infinity Interactive, Inc.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.