29. Abuse of Class Hierarchy

Part of CS:2820 Object Oriented Software Development Notes, Spring 2021
by Douglas W. Jones
THE UNIVERSITY OF IOWA Department of Computer Science

 

An Aside?

A student approached me offering code sort of like this:

class Basic {
    void someMethod() {
        if (this instanceof SubClassOne) {
            SubclassOne thisOne = (SubclassOne)this;
            operateOn( thisOne.somefield );
        } else {
            SubclassTwo thisTwo = (SubclassTwo)this;
            fiddleWith( thisTwo.otherfield );
        }
    }
}

class SubClassOne extends Basic {
    SomeClass somefield;
}

class SubClassTwo extends Basic {
    OtherClass otherfield;
}

The student was having problems with this. My observation was that, of course this code could be made to work, but this code is also significantly inefficient and it abuses the spirit of object oriented programming with class hierarchies.

Also, the above code is historically importat. Before the invention of objects, programmers in languages such as C and Pascal would write code like this in order to handle the kinds of problems that motivated the development of object-oriented languages.

Object-oriented languages allow us to replace this with something better better:

abstract class Basic {
    abstract void someMethod();
        // a concrete class could be used here too
}

class SubClassOne extends Basic {
    SomeClass somefield;
    void someMethod() { // overrides the basic version of SomeMethod
        operateOn( thisOne.somefield );
    }
}

class SubClassTwo extends Basic {
    OtherClass otherfield;
    void someMethod() { // overrides the basic version of SomeMethod
        fiddleWith( thisTwo.otherfield );
    }
}

Why is this better?

This puts all the code that deals with fields of each subclass in that subclass instead of making the superclass aware of those fields. It might even allow us to make the fields of the subclass private where the original scheme forces the fields to be public or as good as public.

It is worth noting that this is not always the most sensible way to do things. Specifically, if we had a factory method that allocated either an instance of SubclassOne or SubclassTwo the sensible place for this factory method will likely be in class Basic, since it always returns an instance of a subclass of Basic. If part of the initialization of the subclass instance by the factory method is specific to the type of that instance, it could be done in a method of the instance, but if that method simply sets a public field of the new instance, it would be just as easy to set that directly in the factory method.

Another Abuse

A student came to me with code like this:

float f = ...

int s = Integer.parseInt( f.toString().split( '.' )[0] );

It took me a while to figure out what this did. Comments and breaking the computation into stages helps:

float f = ...

String fs = f.toString(); // the textual representation of f

String[] afs = f.split( '.' ); // afs[0] -- the text before the point
                               // afs[1] -- the text after the point

int i = Integer.parseInt( afs[0] ); // i is the integer part of f

This was amazing! The student could have written i=(int)f to do the same thing more safely.

The above code is quite unsafe! It works if the exponent part of f is small enough that the toString method converts the number to the simple 000.00 form, but it fails wildly when toString decides to use exponential form like 0.000E15 which means 0.00×1015.

Also, the above code is incredibly slow compared to the simple alternative. The simple alternative is just one machine instruction on almost all modern computers, and is comparable in speed to a floating point add in Java. In contrast, the version using toString has to create a new string object, then create an array object and two new string objects, copying bits of the original string into the two new strings, before finally doing a text to int computation. This is a total of 4 object creations, plus all the multiplication and division involved in conversion between the internal binary data formats and the decimal text formats.

If ever you are tempted to convert an object to textual form, then do text manipulation, an then convert it back to internal form, you are almost certainly wrong!