abstract static

A friend lamented to me last week about the absence of abstract static members from class definitions in Java:

Turns out you can’t do it in C# either. Common understanding is that the absence of such a feature from these languages is an limitation in the language implementation (Java, C#). I’ve come to the realisation that there might be a very good reason as to why you can’t have abstract static members in these languages, and I think it’s due to a problem with the way we understand the static keyword.

I think we might be thinking about the static keyword wrongly. The static keyword is used for instance-independent functions and data. Instance-independence, however, doesn’t really make sense in an object-oriented context.I’ll explain this in a second, but fundamental to understanding that is an understanding of the way that programmers view class definitions.

What do class definitions really represent?

The tweet presented at the start of this article perfectly represents the way that many programmers think about class definitions. There are two main components here:

  1. Class definitions represent a type of object, and
  2. A class definition defines a set of properties common to all objects of that type.

With such an understanding, it’s easy to see where the need for an abstract static member comes from. Like in the Porsche example earlier, sometimes, each subclass of a class will share a common property, and this property should be implemented to be common across all instances of the subclass.

The difficulty in this understanding is that it’s very nearly accurate. What I think a lot of programmers get wrong when it comes to OOP is that class definitions do not represent a type of object, but represent a contract of characteristics that each instance is guaranteed to fulfil.

In the same way that classes implement interfaces, instances (objects) implement class definitions. In their purest form, class definitions specify specific data and functions that class instances will hold - no more, no less. They make no guarantee of type or commonality between instances of the same class.

This is wholly consistent with the notion of classes as objects - our superclass Car simply requires all instances to fulfil the contract of having a Make and Model, and cares not for the implementation details, including whether there are subsets of Cars with common Makes or Models. Asking the superclass to understand this by marking its members with abstract static requires a tighter coupling than should be used in such scenarios.

Implications for the static keyword

If a class definition is simply a contract of characteristics, or an interface that instances are expected to fulfil, then the notion that static members represent common properties of the type is wrong.

What are static members actually for, then?

I’d argue that static members are simply a convenience to the programmer, somewhat divergent from true object-oriented design. In my own experience, I’ve only ever used static members when I’ve somehow needed to manage or coordinate multiple instances of a class. Take a look at the following class definition, defining a member contract for an animal on a farm (example in C#):

class Animal {
    String _uniqueID;
    public String UniqueID {get { return _uniqueID; }}

    public Animal() {
        _uniqueID = getUniqueID();
    }

    public static HashSet<string> _uniqueIDs;
    public static Animal() {
        _uniqueIDs = new HashSet<string>();
    }
    public static String getUniqueID() {
        //code to generate ID and check it's not already in use,
        // i.e. in _uniqueIDs
    }
}

Our Animal class is split into two parts - the non-static members define the actual data pertaining to the Animal contract, and the static members simply manage the integration of the Animals within their broader context.

You could just as easily express the same functionality like this:

class Animal {
    String _uniqueID;
    public String UniqueID {get { return _uniqueID; }}

    public Animal(String UniqueID) {
        _uniqueID = UniqueID;
    }
}

class Farm {
    public static HashSet<string> _uniqueIDs;
    public static Farm() {
        _uniqueIDs = new HashSet<string>();
    }
    public static String getUniqueID() {
        //code to generate ID and check it's not already in use,
        // i.e. in _uniqueIDs
    }

    public Animal MakeAnimal() {
        return new Animal(getUniqueID());
    }
}

Using this architecture, Animals are still guaranteed to have unique IDs within their broader context - Farm objects - without having coordination logic embedded within the Animal class.

If you’re not expressing your class in this format, you’re probably violating the Single-Responsibility Principle - you’ve simultaneously got a class that is used for SomethingOrOther-ing and the static aspects of that class, that coordinate SomethingOrOthers.

Here’s a challenge for you - try re-writing the static keyword out of some of your old code. It may take a little getting used to, and it’s certainly not always necessary, but it will be helpful for understanding that static is a convenience keyword only, and not an integral part of Object-Oriented design.

A final note on Porsches

As for our opening example - “all Porsches have the name ‘Porsche’” - I disagree; you’re thinking about classes wrong. “Porsche” is the make of the individual car, and the fact that it is a value common to a subset of Cars should merely be a coincidence to the Car superclass. I’ve got no problem with thinking about this specific issue without the use of the static keyword, with:

class Porsche : Car {
    public override string GetMake() {
        return "Porsche";
    }
}