Suppose we have a class-hierarchy whose base class B
requires a clone()
method to be defined in its derivations. Whilst this works perfectly for most of them there is this one derivation X
that holds an optional reference to a resource/object that can only be referenced from one instance of X
. Whilst the rest of X
can be cloned without problems this reference would have to be set to null/nil/nullptr.
What is the correct way to handle this situation?
X::clone()
could throw anUncloneableException
. Though this would be correct, it is not satisfying.- Set the reference to null/nil/nullptr and leave no notice of it. The user of
X
has to know aboutX
s “particularities”. - Require derivations of
B
to defineB::nearestClone()
: the user ofB
has to be aware that the return value might not be a 1:1 clone. - Something else?
3
You don’t specify the language you’re using, but I would suggest that one approach would be to have the base class should have a protected
method cloneBase
, and then for each class which could support cloning have a sealed concrete derivative which implements a covariantly-generically-typed cloning interface, with itself as the generic type parameter. It may be useful to have this interface also include a method which implementations will use to return themselves, typed according to the generic parameter.
If AnimalBase
would have no difficulty supporting cloning, but some derived classes may have non-cloneable objects, then Animal
should be a sealed class which derives from AnimalBase
and implements ICloneable<Animal>
. If BirdBase
derives from animal, Bird
would be a derivative of that which implements ICloneable<Bird>
. If Phoenix
derives from Bird
but can’t support cloning, neither it nor any derivative would implement any ICloneable
interface.
Using this approach, code which needs something that is cloneable and derives from AnimalBase
could accept a parameter of type ICloneable<AnimalBase>
; such a type could accept either Animal
or Bird
, even though those types are unrelated. Code which needs to use an ICloneable<AnimalBase>
as an AnimalBase
could use its Self
member.
Alternatively, one could implement an IsCloneable
property/method and then have a Clone
method which is only guaranteed to work if IsCloneable
returns true. Such an approach would provide no way for a type to indicate at compile time that it needed something that can be cloned, but it would avoid the need for extra sealed “leaf” types. While there is some benefit to having methods express their requirements via their parameters, there is also benefit to avoiding the creation of otherwise-unnecessary types.
Under the constraints of the question, I would have an interface such as this (pseudocode):
public interface Cloneable<T> {
boolean isCloneable();
T clone();
}
The isCloneable()
method would return true if the current object can be cloned, while the clone()
method would perform the actual clone. This is a kludgy hack for the fact that a subclass might not be cloneable despite implementing the Cloneable
interface. If your programming language already provides a Cloneable
interface you may need to add your own to make this work.
In fact this entire exercise serves to prove that a Cloneable
interface is a bad idea. It places a constraint on any class that directly or indirectly implements the interface, and potentially any class contained in Cloneable
objects.
Your question makes the assumption that cloning must be supported. However, the ideal way of copying an object is not to have a Cloneable
interface, but to bake the idea of “copying” directly into a class without using the type system to define what is and is not cloneable. That is out of the scope of your question, but it might be worth exploring as a separate exercise (and perhaps follow-on question).
1
Sounds like one particular instance of X is the “alpha” X and is, in effect, a singleton. Breaks some design principles but might still be reasonable.
The strict purist approach is that alphaX is not clonable, nor persistable, etc. so nobody can break the singleton contract. This is actually tricky and has generated many articles and books. Your options are to make all Xs non-cloneable, or for just alphaX to throw an exception. Can the user know if their particular X is the alpha?
However, if this is more like nodes in a tree where there is one special root node, but clone still makes some sense (whether you clone the parent and children is a good question) you could clone but set that reference to null.
So, “it depends”. Sorry that this isn’t definitive. 🙂