这是Objective-C的类设计问题。这是一个例子:
文件系统包含文件和目录。两者都是“节点”。例如,走一个目录会产生一个节点列表,一些是[sub]目录,另一些是文件。
这指向类层次结构的以下客户端抽象视图:
@interface Node: NSObject {}
@end
@interface Directory: Node {}
@end
@interface File: Node {}
@end
到目前为止一切顺利。在这一点上,所有三个类都是抽象的。现在开始实现,您会发现有两条主要路径:使用URL(Apple推荐用于Mac OSX≥10.6)或路径(仅适用于Mac OSX≤10.5或Cocotron)。
现在,您需要为上面三个抽象类中的每一个开发两个具体实现:
// Node subclasses
@class NodeWithPath;
@class NodeWithURL;
// Directory subclasses
@class DirectoryWithPath;
@class DirectoryWithURL;
// File subclasses
@class FileWithPath;
@class FileWithURL;
现在考虑一下FileWithURL
:
File
继承。NodeWithURL
但是File
和NodeWithURL
不在同一个类层次结构行中。没有多重继承,就无法在Objective-C中表达它。
那你怎么设计这种情况呢?我可以看到两个想法:
我倾向于赞成协议的想法。在这种情况下,Directory
和File
将是协议,并且六个具体类将从公共Node
超类继承并符合其对应协议。 Node
将有两个子类层次结构:一个使用URL,一个使用Paths。
现在存在从客户端代码隐藏实现的问题。可以使用Node
公共超类为此目的设置类集群。客户端代码会根据具体情况将对象输入为Node<File>
或Node<Directory>
。
任何其他/其他/类似/不同的想法?
答案 0 :(得分:3)
也许我错过了一个明显的问题,但是......为什么你需要一个URL和一个对象的路径实现?您似乎可以将路径存储为URL,并根据需要在两者之间进行转换。您的课程的合理实施可能是:
@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
在更一般的情况下,可能更容易将协议用于顶级“对象”,然后让每个具体实现实现协议。您还可以使用类集群来使公共接口更清晰,因此您只需要File
和Directory
类,而不是每种类型的后备存储类。这也允许您在删除对旧版本框架的支持时轻松交换实现。像这样:
#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
我遗漏了Directory
的实现,但它会类似。
我想你甚至可以走得更远。在Unix上,目录是一个具有一些特殊属性的文件,因此Directory
甚至可以从File
继承(虽然这对类集群来说有点难看,所以请谨慎操作如果这样做的话。)
答案 1 :(得分:1)
如果您需要支持缺少NSURL采用所需方法版本的系统,只需使用路径即可。然后,当您放弃对这些系统的支持时,转换过来,这将需要20分钟。如果您必须使用这个超级复杂的系统来管理它们,那么使用URL几乎肯定不值得。
答案 2 :(得分:0)
我不相信在这种情况下有一个很好的理由来分开子类只是为了注意数据的来源(URL或路径 - 并且路径可以表示为文件:// URL)。
我的感觉是另一种模式更适合这种情况。我认为这是装饰器模式 - 只为每个文件配备一个“source”属性,在这个例子中可以是URL或文件相关的。在处理对象时它非常方便,因为有关此属性的整个逻辑可以放入这些辅助对象中。以后也很容易延长。
在一般情况下,我认为协议是要走的路,但你应该总是问自己是否真的需要表达差异(这里是URL与文件)。通常,该代码的用户(甚至是库本身)根本不应该关心。
答案 3 :(得分:0)
您可以完全将路径/ url属性与节点分离;它们更像是节点层次结构的隐式属性而不是其中一个节点本身,并且您可以轻松地从节点计算一个或另一个,前提是它们都有父节点。
如果您使用不同的工厂来创建路径/网址,则可以在不触及节点层次结构类的情况下交换或扩展命名系统。
沿着这条路径继续,如果将所有文件操作移动到单独的类中,则在Node层次结构中没有依赖于版本的代码。