Sean Murphy ably describes how an abstract class cluster works in Objective-C. NSString and every collection class is by contract a class cluster, and this is a useful pattern.
However, I take some exception with the example offered:
@interface SearchPluginParser
- (id)initWithPluginMIMEType:(NSString *)mimeType;
- (BOOL)parseSearchPluginAtURL:(NSURL *)searchPluginURL;
- (NSURL *)searchPluginURL;
- (NSString *)searchPluginName;
@end
Let’s see what can happen: you can init a parser. You can then either go parse a search plugin or look at the parsed search plugin’s URL and name… what? There’s implicit state built into the design. It’s implicitly part of the contract that you have to message parseSearchPluginAtURL: to go ahead with the rest of the class, and that searchPluginURL and searchPluginName only make sense after a search plugin has successfully been parsed. Even with that information, say you parse a search plugin and then parse an invalid search plugin. What’s searchPluginURL return now? nil? The URL of the last successful search plugin parsed? It’s horribly non-Cocoa-esque, but you also can’t exclude the possibility that it will throw an exception. (The Cocoa-tasting alternative is to highlight that the method may sometimes be expected to fail in a certain way due to network circumstances or uncontrolled data, and offer an NSError pointer out. Exceptions are more of an “oh shit!” expression which can be handled just for the sake of not bringing the application down.)
I’ve recently taken to an axiom: because there are more of them from many sources, it’s getting harder and harder to tell what you can do with objects, so a well designed class is one where the valid operations of the objects is always exactly what is available. I’m the first to admit that this kind of thinking can lead to brutal over-engineering, if applied ruthlessly or without thinking. In this way, it differs not from any other tool available for designing classes or the flow of objects.
The fun continues:
@interface SearchPluginParser (AbstractMethods)
// Abstract methods which should be implemented by subclasses:
- (BOOL)parsePluginData:(NSData *)searchPluginData;
@end
@interface SearchPluginParser (SubclassUseOnly)
// Private, concrete methods which should only be used by subclasses:
- (void)setSearchEngineName:(NSString *)newSearchEngineName;
- (void)setSearchEngineURL:(NSString *)newSearchEngineURL;
@end
The idea of class clusters seems somewhat missed. The idea, which Sean brings up, is to abstract away the concretions, and only depend on the abstractions. Let’s take a look at NSString‘s header file:
@interface NSString : NSObject <NSCopying, NSMutableCopying, NSCoding>
/* NSString primitive (funnel) methods. A minimal subclass of NSString
just needs to implement these, although we also recommend
getCharacters:range:. See below for the other methods.
*/
- (NSUInteger)length;
- (unichar)characterAtIndex:(NSUInteger)index;
@end
NSString in itself is never instantiated, and provides base implementations of most of the surrounding methods. But the core is abstract. NSString handles the higher order operations, presuming that there is always a way to accomplish the lower level goals. The SubclassUseOnly category in the example, contrarily, prescribes a concrete storage for the data. (The data that, as we recall, is variously valid or not valid to request, depending on if you’ve called the parse method before.) It’s not particularly cumbersome to provide for your own storage, and it’s one of the things that should be concretely decided. But worse than that, it adds methods in the interface.
With that in mind, let’s remake the example.
@interface SearchPlugin
+ (id)searchPluginWithMIMEType:(NSString *)mimeType
atURL:(NSURL *)searchPluginURL;
- (NSURL *)url;
- (NSString *)name;
@end
@interface SearchPlugin (AbstractMethods)
- (id)initWithMIMEType:(NSString *)mimeType
fromURL:(NSURL *)searchPluginURL data:(NSData *)data;
@end
Some comments about this proposal:
The parser part of the equation becomes part of the implementation details handled by the concrete subclasses. Instead, the class is a search plugin. Given that parsing for a particular kind of search plugin (OpenSearch, Sherlock/Mycroft) is handled by a subclass, it doesn’t seem like much of a stretch.
The abstract superclass also handles going to the network. A well implemented example of this class cluster would check if the mime type is supported by some concrete subclass, then fetch the data over the network, then initialize an object of the right subclass, handing it the origin URL and the actual data as well as the MIME type. The origin URL may actually be required to resolve relative paths in the search plugin specification (although I’d call that bad form for an authoritative document), so it seems strange not to pass it to the method that does the parsing.
The design is more geared towards the client. I’m guessing that the client is not fascinated by whether the network failed, whether the URL was blacklisted in the phishing list or whether the format was invalid; it wants to get on with the rest of the search plugins it wants to deal with. (The client might be interested in these details, but such errors can be very implementation specific. Nevertheless, an
NSErrorparameter out can be added with plenty of precedence from Cocoa itself.)If constructing such a search plugin is possible, there’s no ifs and buts anymore: if it’s not
nil, it’s valid, and the accessors (which could probably be made into readonly properties) work just fine. The state situation from before has evaporated. Additionally, they can be put into a collection, something which is impossible for the parser; I’d imagine many clients of this class cluster would want to scan a web page for search plugins and return a bunch of them.This is still evidently a “godfather” variety class cluster, in which the class cluster knows every eventuality from the start. You could make it an inefficient (O(n)) open class cluster by simply iterating through every subclass and try to initialize objects of their class (if they’re handed data they can’t deal with, they return
nil). However, with the addition of one abstract class method (+ canProvideSearchPluginWith...), you could instead make it an open class cluster, where you could implement a loose check (for MIME type) separately.
The reason I don’t write more about class design is because it’s easy to get lost in it. I’m a bit sickened by the love for design patterns, although I do understand why they’re there. The last point I made, a subclass hierarchy that could be meta-interrogated for support, is a big deal in languages with a lesser object model, like C# and Java. If you want to emulate the elegance of it, you have to build some ugly scaffolding, but a by-product of that is people who want to talk for hours about the Factory pattern, which is what it’s called when you don’t have class methods.
Me, I just want my objects to contain what they contain, do what they do, and get on with hooking them together. I can’t wait until that becomes a “best practice”.
Hey Jesper,
Thanks for discussing my article, and even more so taking time to read what I had to say in the first place. Moreover, I appreciate the useful and constructive commentary of my approach, and I certainly respect and concur with the points you made.
I will say, the foremost aspect I was attempting to convey and offer with the article was the benefit and general technique of the abstract factory design pattern itself. My specific, concrete, implementation of search plugin parsing was put forward mainly as a way to better illustrate the overall design and its advantages, through an explicit example rather than solely overview and theory.
The actual implementation details of the search plug-in parser, as expressed in the article, are albeit a bit simplified and not as exhaustively thought-out as the full design I ended up committing to Camino. For anyone curious, you can view the full patch I submitted. Furthermore, I also feel compelled to mention that over-engineering was indeed something I discussed with our team as well. Rather than deriving motivation from a love for design patterns, for example, this was a case where we anticipated a likelihood of change in the future and went with a design approach we thought could offer us real benefit in the future (we weren’t convinced OpenSearch was the be all end all format).
This was the very first article I published. While I haven’t always been able to find the time to compose and share my thoughts regularly, the fact that something I wrote was worthy of conversation, and allowed me to connect and engage with other developers I respect, makes it surely worth the effort and gives me the motivation to write further.
I’m a regular reader of your writing, and really enjoy and strike a harmony with your commentary. Thanks again for mentioning my work, I’m delighted to be featured here.
By Sean Murphy · 2010.01.02 18:00
Thanks for taking this article in exactly the spirit it was written. (I was a bit worried that it might seem like some kind of entry into oneupmanship, but if we can’t discuss this sort of design and weigh pros and cons, what can we discuss?)
I well appreciate the work your article did around explaining the concept, and I understand that if you move your focus towards eminent class design, it’s less pedagogic and takes a lot more time to write. I also understand that it’s not necessarily what goes into Camino. (I deliberately chose to leave any mention of Camino out of the article altogether to avoid any confusion. I find that the Camino code I’ve seen is generally excellent.)
The more concepts you have to grapple with, the less effective the article becomes. If I were to attack one thing in the article, it would be that most of what I took exception to were some things that, as I see it, could have been omitted entirely as exercises left to the reader, which would have made for even more simplicity, even though you can’t cut all of that or you wouldn’t have an actual example left to show if that was the goal.
What got me especially was the subclass-proscribed setters, because an abstract class, like you say, provide abstractions, and the idea of setters to explicitly only call from subclasses is clearly a concretion when the storage is handled by the abstract class. “Protected” property setters or accessors being available to subclasses only is a different concept, one without which it would be hard to design anything, but aren’t these things covered simply by basic subclass design? If you’re interested in making an abstract class cluster, wouldn’t you already be able to cope with stuff like that? I see it as something that could happily be written off as outside the scope of the article, is what I’m saying.
And to be completely clear, I didn’t mean to attack anyone in particular over design patterns. I just have a day job writing C# (which I also love) and sadly, you can’t swing a cat around that (or the Java) community without hitting a few people with enormous hardons for the concepts without knowing how or when they’re effectively applied. I vented a little.
Once again, I appreciate the article and it certainly does a lot more good than bad. I look forward to reading more of them.
By Jesper · 2010.01.02 18:33