Jean-Denis Muys
Jean-Denis Muys

Reputation: 6852

Objective-C organization of hierarchies of class clusters

This is a question of class design with Objective-C. Here is an example:

File systems have files and directories. Both are "nodes". Walking a directory for example yields a list of nodes, some being [sub]directories, other being files.

This points to the following client-side abstract view of the class hierarchy:

@interface Node: NSObject {}
@end

@interface Directory: Node {}
@end

@interface File: Node {}
@end

So far so good. At this point, all three classes are abstract. Now going to implementation, you realize there are two main routes: using URLs (recommended by Apple for Mac OS X ≥ 10.6), or paths (only possible way for Mac OS X ≤ 10.5 or Cocotron).

So now, you need to develop two concrete implementations of each of the three abstract classes above:

// Node subclasses
@class NodeWithPath;
@class NodeWithURL;

// Directory subclasses
@class DirectoryWithPath;
@class DirectoryWithURL;

// File subclasses
@class FileWithPath;
@class FileWithURL;

Now consider, say, FileWithURL:

But File and NodeWithURL are not within the same class hierarchy line. Without multiple inheritance, there is no way to express that in Objective-C.

So how would you design this situation? I can see two ideas:

I tend to favor the protocol idea. In that case, Directory and File would be protocols, and the six concrete classes would inherit from a common Node superclass and conform to their counterpart protocol. Node would have two subclass hierarchies: one using URLs, one using Paths.

Now there is the issue of hiding the implementation from the client code. A class cluster can be setup for this purpose with the Node common superclass. Client code would get objects typed as Node<File> or Node<Directory> as the case may be.

Any additional/other/similar/different ideas?

Upvotes: 3

Views: 1075

Answers (4)

molbdnilo
molbdnilo

Reputation: 66459

You could decouple the path/url property from nodes entirely; they're more an implicit property of the node hierarchy than one of the nodes themselves, and you can easily compute one or the other from a node, provided they all have a parent.

If you use different factories for creating your path/urls you can swap or extend your naming system without touching your node hierarchy classes.

Continuing along this path, if you move all file operations into separate classes you'll have no version-dependent code in your Node hierarchy.

Upvotes: 0

mipadi
mipadi

Reputation: 411222

Maybe I'm missing an obvious problem, but...why do you need both a URL and a path implementation of object? It seems like you could just store the path as a URL, and convert between the two as necessary. A reasonable implementation for your classes could be:

@interface FileSystemNode : NSObject
{
    NSURL *URL;
}
@property (retain) NSURL *URL;
@property (retain) NSString *path;
- (id)initWithURL:(NSURL *)aURL;
- (id)initWithPath:(NSString *)aPath;
@end

@implementation FileSystemNode

@synthesize URL;

- (id)initWithURL:(NSURL *)aURL
{
    if ((self = [super init])) {
        [self setURL:aURL];
    }
    return self;
}

- (id)initWithPath:(NSString *)aPath
{
    return [self initWithURL:[NSURL fileURLWithPath:[aPath stringByExpandingTildeInPath]]];
}

- (void)dealloc
{
    [URL release];
    [super dealloc];
}

- (NSString *)path
{
    return [[self URL] path];
}

- (NSString *)setPath:(NSString *)path
{
    [self setURL:[NSURL fileURLWithPath:[path stringByExpandingTildeInPath]]];
}

@end

@interface File : FileSystemNode
@end

@interface Directory : FileSystemNode
@end

Update (based on comments)

In the more general case, it may be easier to use a protocol for the top-level "object", and then have each concrete implementation implement the protocol. You could also use class clusters to make the public interface cleaner, so you just have File and Directory classes, instead of one for each type of backing store. This would also allow you to easily swap out implementations when you drop support for older versions of the framework. Something like this:

#import <Foundation/Foundation.h>

// FileSystemNode.h
@protocol FileSystemNode
@property (readonly) NSURL *URL;
@property (readonly) NSString *path;
@end

// File.h
@interface File : NSObject <FileSystemNode>
- (id)initWithURL:(NSURL *)aURL;
- (id)initWithPath:(NSString *)aPath;
@end

// File.m

@interface URLFile : File
{
    NSURL *URL;
}
- (id)initWithURL:(NSURL *)aURL;
@end

@interface PathFile : File
{
    NSString *path;
}
- (id)initWithPath:(NSString *)aPath;
@end

@implementation File

- (id)initWithURL:(NSURL *)aURL
{
    [self release];
    return [[URLFile alloc] initWithURL:aURL];
}

- (id)initWithPath:(NSString *)aPath
{
    [self release];
    return [[PathFile alloc] initWithPath:aPath];
}

- (NSURL *)URL
{
    [self doesNotRecognizeSelector:_cmd];
}

- (NSString *)path
{
    [self doesNotRecognizeSelector:_cmd];
}

@end

@implementation URLFile

- (id)initWithURL:(NSURL *)aURL
{
    if ((self = [super init])) {
        URL = [aURL retain];
    }
    return self;
}

- (NSURL *)URL
{
    return [[URL retain] autorelease];
}

- (NSString *)path
{
    return [URL path];
}

@end

@implementation PathFile

- (id)initWithPath:(NSString *)aPath
{
    if ((self = [super init])) {
        path = [aPath copy];
    }
    return self;
}

- (NSURL *)URL
{
    return [NSURL fileURLWithPath:path];
}

- (NSString *)path
{
    return [[path retain] autorelease];
}

@end

I left out the implementation of Directory, but it would be similar.

You could even go farther, I suppose. On Unix, a directory is a file with some special properties, so maybe Directory could even inherit from File (although that gets kind of ugly with class clusters, so exercise caution if doing so).

Upvotes: 3

Catfish_Man
Catfish_Man

Reputation: 41831

If you need to support systems that lack NSURL-taking versions of methods you need, just use paths. Then when you drop support for those systems, convert over, it'll take like 20 minutes. The efficiency gains of using URLs are almost certainly not worth it if you have to have this super-complicated system to manage them.

Upvotes: 1

Eiko
Eiko

Reputation: 25632

I am not convinced that there is a good reason for separate subclasses in this case just to note the origin of the data (URL or path - and a path could be expressed as a file:// URL anyway).

My feeling is that another pattern would suit this much better. I think it's the decorator pattern - just equip every file with a "source" property, which can be URL- or file-related in this example. It can come very handy when working with objects, as the whole logic regarding this property can be put into these helper objects. It is easy to extend later, too.

In the general case I think protocols are the way to go, but you should always ask yourself if you really need to express the difference (here URL vs. file). Often, the user of that code (even the library itself) shouldn't care at all.

Upvotes: 0

Related Questions