Pitfalls with self-replicating objects

by David Le Bansais (modified: 2010 Apr 01)

When designing a class, it's a good thing to consider reusability. The subject of this entry is to examine objects of a class that are able to replicate, and the consequences this has on making the class reusable.

Object replication means creating a new object from within the code. It doesn't have to be a copy, it just means here that a new object of the same class is created. There is no special consideration regarding the state of the new object.

Let's take a look at some sample code:

class BASIC_CELL create make feature -- Creation make do chromosomes := 1 end feature -- Properties chromosomes: INTEGER set_chromosomes (n: INTEGER) require n > 0 do chromosomes := n ensure chromosomes = n end feature -- Mitosis mitosis: like current do create result.make result.set_chromosomes(chromosomes) ensure result.chromosomes = chromosomes end invariant chromosomes > 0 end

The effect of calling mitosis is to create a new cell identical to the original.

Inheritance

All is good so far, however things become messy if we want to write a new class inheriting from BASIC_CELL. You noticed the create intruction, it calls make and therefore make must be available for creation for all descendants.

This can be a problem, for instance if the descendant has no obvious default state. It's also a problem if we don't want the outside world to create objects of the descendant class with make. Fortunately, a work around this is to restrict the export state of make, as the code below demonstrates.

class HUMAN_CELL inherit BASIC_CELL create make_with_organ create {HUMAN_CELL} make feature -- Creation make_with_organ (organ: ORGAN) do make cell_organ := organ end feature -- Properties cell_organ: ORGAN set_organ (organ: ORGAN) do cell_organ := organ end end

This code fails to compile if void safety is turned on, and types are attached by default. In that case, there is no way for make to find the correct value of cell_organ.

Deferred descendants

Theoretically, a deferred class can inherit from a parent without trouble. But no deferred class can inherit from BASIC_CELL, because it would have to list make as a creation procedure, and deferred classes cannot have them.

Fixing the code

A good workaround to this problem is to have a feature dedicated to the creation of the new object. The idea is that it can be undefined and redefined based on the descendants needs. Undefined in deferred classes, redefined to use the appropriate creation procedure otherwise.

For BASIC_CELL, the code becomes:

class BASIC_CELL create make feature -- Creation make do chromosomes := 1 end feature -- Properties chromosomes: INTEGER set_chromosomes (n: INTEGER) require n > 0 do chromosomes := n ensure chromosomes = n end feature -- Mitosis mitosis: like current do result := create_new result.set_chromosomes(chromosomes) ensure result.chromosomes = chromosomes end feature -- New object create_new: like current do create result.make end invariant chromosomes > 0 end

class HUMAN_CELL inherit BASIC_CELL redefine create_new end create make_with_organ feature -- Creation make_with_organ (organ: ORGAN) do make cell_organ := organ end feature -- Properties cell_organ: ORGAN set_organ (organ: ORGAN) do cell_organ := organ ensure cell_organ = organ end feature -- New object create_new: like current do create result.make_with_organ(cell_organ) end end

Here is an example of a deferred class with the undefine clause. Redefining the create_new feature is left to next descendants in the hierarchy.

deferred class BLOOD_CELL inherit HUMAN_CELL undefine create_new end feature -- Marker add_marker deferred end end

class BLOOD inherit ORGAN end

class RED_BLOOD_CELL inherit BLOOD_CELL undefine make end create make feature -- Creation make local body_blood: BLOOD do create body_blood make_with_organ(body_blood) end feature -- Marker add_marker do end feature -- New object create_new: like current do create result.make end end

Conclusion

As we have seen, using create to duplicate an object makes a class harder to reuse, unless the use of create is restricted to be in a dedicated feature. Descendants can then undefine or redefine that feature in a way that suits their needs.

The compiler could detect this situation and issue a warning if the feature where create is found to contain other instructions.

Last but not least, perhaps someone could take a look at classes from the standard libraries, since at least TWO_WAY_LIST can replicate. I have myself written code that inherits from TWO_WAY_LIST, and working around this issue has annoyed me quite a bit, prompting this post.

Comments
  • Manu (7 years ago 2/4/2010)

    The issue with deferred classes has been discussed at ECMA. At the time, we postponed any resolution on the solution because it was not felt as a major issue and it is easy to work around using the `create_new' mechanism you have described and the pitfall you mentioned.

    I believe that if something is adopted it will be the following. When checking the code of a deferred class the creation of a like Current' instance will always be valid if the call is valid. Clearly the non-deferred descendants of the deferred class will ensure that make' is indeed properly exported for creation.