Creating Adapters Using Protocols
I have some code I’ll be open sourcing in the next month or so. It uses a third party JSON library to convert Objective-C objects to JSON objects. When I wrote it, the most popular JSON library out there was TouchJSON. Today, although TouchJSON is certainly still popular, there are many other JSON parsing libraries available.
That said, in general, I’d say it’s poor form to require third party libraries when distributing an open source component. It’s difficult enough for people to integrate your code into their projects, let alone bringing in even more code from other libraries. Furthermore, what if the consumer of your component already has chosen a library that does what you need the third party library to do and doesn’t want to add yours?
Decoupling Is Good For You
The root of this problem has to do with coupling. When your code is coupled closely to specific libraries or other classes that you do not provide as part of your distribution, it decreases its reusability.
Because of these issues, I’d prefer not to distribute this code if it’s going to force users to be locked in to using TouchJSON.
Fortunately, Objective-C provides us with some awesome tools to help reduce coupling. The tool I’d like to talk about today is protocols and how you can use protocols to decouple your code from specific frameworks by using it to implement the Adapter pattern.
Even though TouchJSON has various methods for encoding objects to JSON from NSDictionary, NSArray, and other foundation container classes, remember that we don’t want to couple ourselves to TouchJSON. We want to make it possible to use our code with any JSON encoding library. So what we need is to tell users of our code exactly what we need from a JSON library so that they can implement adapters for whatever library they want to use.
This is a perfect case for using protocols.
A protocol allows us to define an interface that other objects can implement. We can then write our code according to the protocol and not the specific interface of the library. This way, users of our code can pass in any object that conforms to the protocol – including our adapter.
Let’s look at some code!
First, to define an protocol, you define it using the @protocol directive.
@protocol MyDoodadProtocol
-(NSString *)encodeDictionary:(NSDictionary *)inDictionary;
@end
As you can see, here we’re defining the MyDoodadProtocol protocol. In it, we’ve defined just one method, -encodeDictionary:. This method is our adapter method for talking to whatever JSON encoding framework we’re using. The implementation of this method is left up to our adapter later.
To use the MyDoodadProtocol protocol, we simply need to pass an object into our MyDoodad which conforms to it. We check this when it’s passed in by declaring that the type of the object is id<MyDoodadProtocol>. This limits the type of argument to an object conforming to the protocol. If we pass something else, it’ll generate a compiler error
@interface MyDoodad : NSObject
{
id<MyDoodadProtocol> encoder;
}
-(id)initWithJSONEncoder:(id<MyDoodadProtocol>)inEncoder;
-(void)doStuffWithDictionary:(NSDictionary *)inDictionary;
@end
@implementation MyDoodad
-(id)initWithJSONEncoder:(id<MyDoodadProtocol>)inEncoder;
{
if((self = [super init]))
{
encoder = [inEncoder retain];
}
return self;
}
-(void)doStuffWithDictionary:(NSDictionary *)inDictionary
{
// we already know it implements our protocol,
// so we can use it here
// if we didn't know, though,
// we could check respondsToSelector or
// conformsToProtocol here as well.
NSString *json = [encoder encodeDictionary:inDictionary];
[self doSomethingWithJSON:json]; // ....
}
@end
The actual implementation of the adapter can be as simple as a class which acts as an intermediary. This one uses TouchJSON…
@interface MyDoodadAdapter : NSObject <MyDoodadProtocol>
-(NSString *)encodeDictionary:(NSDictionary *)inDictionary;
@end
@implementation MyDoodadAdapter
-(NSString *)encodeDictionary:(NSDictionary *)inDictionary;
{
// TouchJSON uses "serializeObject"...
NSData *data = [[CJSONSerializer serializer]
serializeObject:inDictionary error:nil];
NSString *ret = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
return [ret autorelease];
}
@end
So by doing this, your code is only having to rely on the protocol, and not on the specific implementation of any given library. Users of your code can pass any object they want into the initializer, and your code will use just the interface it’s declared it needs via the protocol. It’s a nice clean way to decouple your code from dependencies.
Why not use inheritance?
Using a protocol is better than using a class and requiring the user inherit from your class because you never know what kind of class hierarchy your user may already be using. She may want to make the adapter from a class that’s already inheriting from something else, or even simply make a category on the encoding library itself. For example:
@interface CJSONSerializer (MyDoodadAdapter) <MyDoodadProtocol>
-(NSString *)encodeDictionary:(NSDictionary *)inDictionary
{
NSData *data = [self serializeObject:inDictionary
error:nil];
NSString *ret = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
return [ret autorelease];
}
@end
This code actually extends the TouchJSON API directly, and adds the required method directly to the CJSONSerializer class. This can be very convenient and keep us from having to add additional classes to our code. As shown here, categories can be declared to conform to protocols just like classes can.
Is this something I should do all the time?
In a nutshell… no. If you’re using a lot of capabilities from a library, making adapter protocols for everything in the library is impractical and would be a very bad idea. However, if you only need a few methods, providing the ability for users of your code to use an adapter instead of having to be locked into the library you used originally is a great way to provide additional flexibility and make it much easier to integrate your code into their projects. So like all things, use it in moderation and it can be a great tool!

