Tags

, , , , , , , ,

Although I’m categorizing this one as really good advice, rather than as a rule, I think it should be viewed as basically a rule. I think it should be a rule in any object-oriented language that supports it natively (Java and Python, for example).

The advice (rule of thumb, say) is to always create a useful implementation of toString when you create a class. It makes your development and maintenance life ever so much better.

This obviously applies to class design and languages that support the notion (although you’ll thank yourself if you implement it anyway in languages that don’t). It’s because not all languages support it natively that this is advice rather than a rule.

The idea is to give each object the ability to “speak for itself” in development, debug, and other contexts. For example, if you have a list of different objects, and all their classes define toString, it’s very easy to list them with:

for (obj in obj_list) {
    print(obj.toString())
}

In some languages (Python, for example), merely referencing the object in a string context invokes its toString method; there is no need to explicitly invoke the method.

The idea is to make object inspection possible by simply printing it to the console or a log file. To illustrate, suppose we have a 3D vector object with x, y, and z, members. Compare the effort between doing this every time:

print("[%+.3f, %+.3f, %+.3f]", vec.x, vec,y, vec.z)

Versus just doing this:

print(vec)

And getting the same output because you did the explicit version just once in the vector class toString method.

You can put anything that makes sense in the toString method. The idea is to produce a string that is meaningful and descriptive of the object. For a file object, the filename (and perhaps the file size or date) would be useful. For an array or container object, a name or type plus the current size might be useful.

It’s generally not a good idea to put the contents of large objects in toString. That could result in an unexpectedly large output. For large objects, present the size or length. The idea is a brief printable string describing the instance.

As a concrete example, I have a wrapper class for creating images. Its toString method (its Python, so it’s the __str__ method) generates output like this:

[500, 500] RGB (16.6667, 16.6667)

Which describes the object as a 500×500 pixel image using RGB format and its canvas is scaled at 16.666×16.666 (canvas XY distances are multiplied by this scaling factor to get pixel distances).

In some cases, the project requirements may dictate how to use toString to display objects. Which is fine. This advice is about making sure you do it with all your objects. It should be one of the first methods you implement.

§

The name, toString, comes from Java (although I’ve seen it in other languages). Implementing it in Java usually goes something like this (I usually put these methods out of the way at the very bottom of the class definition):

public class Vector3D {
    protected float my_x = 0.0;
    protected float my_y = 0.0;
    protected float my_z = 0.0;
    public Vector3D (float x, float y, float z) {
        my_x = x;
        my_y = y;
        my_z = z;
    }
    /* ... other methods ... */
    public String toString () {
        StringBuffer sb = new StringBuffer("[");
        /* ... build output ... */
        sb.append("]");
        return sb.toString();
    }
}

In Python the same thing might look something like this:

class Vector3D (object):
    def __init__ (self, x, y, z):
        self.x = x
        self.y = y
        self.z = z
    #
    # ... other methods ...
    #
    def __str__ (self):
        t = (self.x, self.y, self.z)
        s = '[%+.3f, %+.3f, %+.3f]'
        return s
    def __repr__ (self):
        t = (self.x, self.y, self.z, hex(id(self)))
        s = '{Vector3D:{x:%+f, y:%+f, z:%+f, id:%s}}'
        return s

Note that Python also has the __repr__() method, which is a secondary “to string” intended more for debugging and object persistence. This gives Python programmers a little more flexibility to distinguish between production-oriented object output and debug/maintenance-oriented output.

As you see, I often use it to create JSON-like strings that contain object property values. Done a bit more formally (perhaps with Python’s JSON module’s help), it offers one way to save, transfer, and load objects.

It’s up to you to implement these methods, and you definitely should.

§

Sometimes it’s also helpful to implement toXml() and toHtml() or whatever else makes sense. These methods, which would be entirely up to you to implement (languages don’t support them), can be handy in environments that deal with XML or HTML.

A toXml() method can be especially handy for object persistence or transmission. The toHtml() method is useful if your objects need to output to the web.

That said, there is a caveat about mixing presentation into the object itself. Usually we prefer to separate data from presentation. It means each class has to have some idea about what to output. It’s usually better to have a central function that knows how to present the data. Or sub-classes that extend your objects for presentation.

But there are simpler projects where your objects live closer to the surface (so to speak), and in those cases toHtml() works pretty well. It would be ideal for objects that are intended for web display, especially if the toString method is used for more normal output (which it really should be).

However, for objects intended for display I’m as prone to make them functional if the language supports it. In Python you just implement the __call__ method, and your objects are callable.

The caveat about separation doesn’t apply quite as much to toXml(), especially if using a standard protocol for how objects emit XML. Such output can be integral enough to the object it makes sense for the object to handle it rather than delegate it. XML, like JSON, is very useful for object persistence.

XML is so useful that in Python, I might be tempted to have the __repr__ method emit XML. Probably I shouldn’t. Emitting JSON, which is my usual goto, is probably bad enough readability-wise. The repr method should probably return something prettier than JSON, let alone XML, but I don’t care.

You probably should, though. (Do as I say, not as I do!)

§

Anyway, bottom line, strongly suggested advice tantamount to a rule where possible:

Always implement tostring!

I swear you won’t be sorry.

Ø