This posted was motivated by Rob Rix’s bug report on NSObject, “Split NSObject protocol into logical sub-protocols”. He notes that NSObject
provides multiple responsibilities[*]: hashing, equality checking, sending messages, introspecting and so on.
What that bug report didn’t look at was the rest of NSObject
‘s functionality that isn’t in the NSObject
protocol. The class itself defines method signature lookups, message forwarding and archiving features. Yet more features are added via categories: scripting support (Mac only), Key-Value Coding and Key-Value Observing are all added in this way.
I wondered whether this many responsibilities in the root class were common, and decided to look at other object libraries. Pretty much all Objective-C object libraries work this way: the Object
class from ObjPak, NeXTSTEP and ICPak101 (no link, sadly) all have similarly rambling collections of functionality.
[*] By extension, all subclasses of NSObject
and NSProxy
(which _also_ conforms to the NSObject
protocol) do, too.
Another environment I’ve worked a lot in is Java. The interface for java.lang.Object
is mercifully brief: it borrows NSObject
‘s ridiculous implementation of a copy method that doesn’t work by default. It actually has most of the same responsibilities, though notably not introspection nor message-sending: the run-time type checking in Java is separated into the java.lang.reflect
package. Interestingly it also adds a notification-based system for concurrency to the root class’s feature set.
C#’s System.Object
is similar to Java’s, though without the concurrency thing. Unlike the Java/Foundation root classes, its copy operation (MemberwiseClone()
) actually works, creating a shallow copy of the target object.
Things get a bit different when looking at Ruby’s system. The Object
class exposes all sorts of functionality: in addition to introspection, it offers the kind of modifications to classes that ObjC programmers would do with runtime functions. It offers methods for “freezing” objects (marking them read-only), “tainting” them (marking them as containing potentially-dangerous data), “untrusting” them (which stops them working on objects that are trusted) and then all the things you might find on NSObject
. But there’s a wrinkle. Object
isn’t really a root class: it’s just the conventional root for Ruby classes. It is itself a subclass of BasicObject
, and this is about the simplest root class of any of the systems looked at so far. It can do equality comparison, message forwarding (which Objective-C supports via the runtime, and NSObject
has API for) and the ability to run blocks of code within the context of the receiving object.
C++ provides the least behaviour to its classes: simple constructors that are referenced but not defined can be generated.
It’s useful to realise that even supposedly simple rules like “single responsibility principle” are situated in the context of the software system. Programmers will expect an object with a “single” responsibility to additionally adopt all the responsibilities of the base class, which in something like Foundation can be numerous.