⌘ kean.blog

Immutability and Builder Pattern

  

We are all aware of the advantages of immutable objects. These is often a desire to make all or most model objects immutable, but they tend to have a lot of properties. How do you initialize such objects without creating a telescoping initializer?

Immutable Objects in Cocoa #

The first idea that comes to mind is to follow the steps of the platform. Cocoa objects often have an immutable and mutable counterpart. A good example of an immutable object is a NSURLRequest which has lots of properties. To construct it, you use its mutable counterpart NSMutableURLRequest. NSMutableURLRequest is a subclass of NSURLRequest which in turn implements NSCopying and NSMutableCopying protocols. We could do the same with our own classes.

Let’s implement a User class and its mutable counterpart:

@interface User : NSObject <NSCopying, NSMutableCopying>

@property (nonnull, nonatomic, readonly) NSString *name;

- (nonnull instancetype)initWithUser:(nonnull User *)user;

@end


@interface MutableUser : User

@property (nullable, nonatomic) NSString *name;

@end
@interface User ()

@property (nonatomic) NSString *name;

@end

@implementation User

- (instancetype)initWithUser:(User *)user {
    if (self = [super init]) {
        _name = [user.name copy];
    }
    return self;
}

- (id)copyWithZone:(NSZone *)zone {
    return [[User alloc] initWithUser:self];
}

- (id)mutableCopyWithZone:(NSZone *)zone {
    return [[MutableUser alloc] initWithUser:self];
}

@end


@implementation MutableUser

@dynamic name;

@end

Now we are able to use those classes the same way we use NSURLRequest. However, there are several problems with this approach:

  • Requires defensive copying to prevent accidental sharing of instances of MutableUser class where immutable User is expected
  • We had to use four lines of code for a single name property
  • We limited our ability to extend class hierarchy

Fortunately, there is an alternative way to create immutable objects which is an overlooked builder pattern. It is a very simple pattern that addresses all those problems.

Builder Pattern #

Let’s dive straight into implementation but this time we will start with a base class for our model objects - Entity.

@class EntityBuilder;

@interface Entity : NSObject

@property (nonnull, nonatomic, readonly) NSString *ID;

- (nonnull instancetype)initWithBuilder:(nonnull EntityBuilder *)builder;

@end


@interface EntityBuilder : NSObject

@property (nullable, nonatomic) NSString *ID;

- (nonnull Entity *)build;

@end
@implementation Entity

- (instancetype)initWithBuilder:(EntityBuilder *)builder {
    // You can also assers that validate builder here
    if (self = [super init]) {
        _ID = builder.ID;
    }
    return self;
}

@end


@implementation EntityBuilder

- (Entity *)build {
    return [[Entity alloc] initWithBuilder:self];
}

@end

Entity class has no mutable counterpart, defensive copying is no longer required. We also used just two lines of code for an ID property.

References