scribble

Andrew Mackenzie-Ross

About Blog GitHub Happy Inspector

23 Jul 2013
Objective-C Lessons from Go

A few months ago, I had a look at the brainchild of a few serious heavyweights working at Google. Their project, the Go programming language, is a static typed, c lookalike, semicolon-less, self formatting, package managed, object oriented, easily paralellizable, cluster fuck of genius with an unique class inheritance system.

It doesn’t have one. Zilch. Nada. Zéro.

“Google – Errrr… Qu'est que fuck??”

But, I dug deeper and needn’t have looked further than chapter 1 of the trusty old Gang of Four for an answer. Two of their favourite techniques for creating lovely software “Program to an ‘interface’, not an ‘implementation’.” and “Favour ‘object composition’ over ‘class inheritance’.” are both exclusively forced upon you in Go. If you’re not familiar with composition, it’s a great design pattern that can negate the need for inheritance and provide a lot greater flexiblity.

In Go, composition of existing classes is trivial and best described in detail in Effective go. But simply, given Reader and Writer classes, a new class ReaderWriter who embeds them immediatley implements both the Reader and Writer interfaces. Fortunately, it’s also very easy to do this in Objective-C and the following code demonstrates an implementation of this with Objective-C message forwarding.

Given a Reader and Writer class.

@interface Reader
- (id)initWithFile:(NSString *)file;
- (NSData *)readError:(NSError * __autoreleasing *)error;
@end

@interface Writer
- (id)initWithFile:(NSString *)file;
- (BOOL)write:(NSData *)data error:(NSError * __autoreleasing *)error;
@end

We can create a ReadWriter class composed of Reader and Writer like so.

// ReadWriter.h
@interface ReadWriter
- (id)initWithFile:(NSString *)file;
@end

@interface ReadWriter (Reader)
- (NSData *)readError:(NSError * __autoreleasing *)error;
@end

@interface ReadWriter (Writer) // by using informal protocols we skip not implemented warnings from the compiler
- (BOOL)write:(NSData *)data error:(NSError * __autoreleasing *)error;
@end

// ReadWriter.m
@interface ReadWriter ()
@property (nontatomic, strong, readonly) Reader *reader;
@property (nontatomic, strong, readonly) Writer *writer;
@end

@implementation

- (id)initWithFile:(NSString *)file {
    self = [super init];
    if (!self) {
    _reader = [[Reader alloc] initWithFile:file];
    _writer = [[Writer alloc] initWithFile:file];
    }
    return self
}

- (id)forwardingTargetForSelector:(SEL)sel {
    if ([_reader respondsToSelector:sel] return _reader;
    if ([_writer respondsToSelector:sel] return _writer;
    return nil;
}

@end

We can now send -[writeData:error:] and -[readError:] to a ReadWriter instance without having to write any extra boiler code. This little snippet makes building composite classes almost as easy as Go.

Composition allows you to selectively “copy” functionality from different classes and expose only the bare minimum interface needed. Testing is a lot easier as you can set the composite at runtime. Just as with inheritance you can overide methods or use default implementations. However, unlike inheritance it is really easy to continue extending behaviour without having to rework classes. And even if you need to, reworking classes is much less of a pain as there isn’t any class inheritance to worry about.

A good example of how composition can work better than inheritance is in the standard Person, Employee, Manager payroll model. Typically, it’s setup something like this.

Person : NSObject
    title
    name

Employee : Person
    salary
    jobTitle
    weeklyPay (salary / 52 weeks)

Manager : Employee
    bonus
    department
    employees
    weeklyPay (super totalPay + (bonus / 52 weeks))

Now what happens if the system needs to change to accomodate a person with two different positions? Uh oh. If we instead reconstructed our class heirarchy and removed one level of inheritance and added a composite position class, our model can now easily support a person with two positions.

Person : NSObject
    title
    name

Position : NSObject
    person
    salary
    jobTitle
    weeklyPay (salary / 52 weeks)

ManagmentPosition : Position
    bonus
    department
    employees
    weeklyPay (super salary + (bonus / 52 weeks))

This is good, but by forcing ourselves to use composition we come up with something even better like this.

Person : NSObject
    title
    name

Position : NSObject
    person (private)
    paymentInformation (private)
    jobTitle
    bonus
    salary
    weeklyPay (super salary + (bonus / 52 weeks)

Department : NSObject
    managementPosition
    employeesPositions

The third model is even simpler and certainly more flexible. It supports multiple positions of different types for any number of persons. We can also easily incrementally expose properties. For example the payroll system might need access to the employees name and their weekly pay, and we might also want to extend the logic around bonuses.

Person : NSObject
    title
    name

Position : NSObject
    person (private)
    paymentInformation (private)
    jobTitle
    employeeName -> person.name
    weeklyPay -> paymentInformation.weeklyPay

PaymentInformation : NSObject
    hasBonus
    bonus
    salary
    weeklyPay (super salary + (bonus / 52 weeks)

Department : NSObject
    managementPosition
    employeesPositions

By simply adding another class and adding it to the position class, we’ve increased flexibility but not at the cost of tight coupling. If we deicide to futher extend this model and give more information to the payroll system. It’s easy with composition. Lets say that the payroll system needs their name, title, weeklyPay and bonus information. We can simply create a composite class to represent this.

Person : NSObject
    title
    name

Position : NSObject
    person (private)
    paymentInformation (private)
    jobTitle
    - newEmployeePayment

PaymentInformation : NSObject
    bonus
    salary
    - weeklyPay (super salary + (bonus / 52 weeks)

Department : NSObject
    managementPositions
    employeesPositions

EmployeePayment : NSObject
    isPaid
    person (private)
    position (private)
    name -> person.name
    title -> position.title
    weeklyPay -> position.weeklyPay
    bonus -> position.paymentInformation.bonus

Hopefully, this example demonstrates that by starting with composition you can often define more succinct interfaces that can be easily maintained and extended in an incremental manner.

As a final task, I tried to replicate some of the functionality from above with inheritance but extending the model for other positions is incredibly difficult as each position requires a subclass and with each subclass the complexity and coupling to the superclasses increases. It is also really difficult to support a person with more than one position or a department with more than one manager.

Person : NSObject
    title
    name

Employee : Person
    salary
    jobTitle
    - weeklyPay (salary / 52 weeks)
    - newEmployeePayment

Manager : Employee
    bonus
    department
    employees
    - weeklyPay (super totalPay + (bonus / 52 weeks))

EmployeePayment
    isPaid
    employee (private)
    name -> employee.name
    title -> employee.jobTitle
    weeklyPay -> employee.weeklyPay
    bonus -> isEmployeeManager? if yes employee.bonus else 0

So, the next time you’re about to make a subclass. Think hard and ask yourself – what would Go do.

- Andy at 19:36

scribble

About Blog GitHub Happy Inspector