3

这是Objective-C的类设计问题。这是一个例子:

文件系统有文件和目录。两者都是“节点”。例如,遍历一个目录会产生一个节点列表,其中一些是 [子] 目录,另一些是文件。

这指向类层次结构的以下客户端抽象视图:

@interface Node: NSObject {}
@end

@interface Directory: Node {}
@end

@interface File: Node {}
@end

到目前为止,一切都很好。此时,所有三个类都是抽象的。现在开始实施,您意识到有两种主要途径:使用 URL(Apple 推荐用于 Mac OS X ≥ 10.6)或路径(仅适用于 Mac OS X ≤ 10.5 或Cocotron的可能方式)。

所以现在,你需要为上面的三个抽象类分别开发两个具体的实现:

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

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

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

现在考虑,比如说FileWithURL

  • 它是一个文件,所以它应该继承自File.
  • 它是一个用 URL 实现的节点,所以它应该继承自NodeWithURL

但是FileNodeWithURL不在同一类层次结构行内。如果没有多重继承,就无法在 Objective-C 中表达。

那么你会如何设计这种情况呢?我可以看到两个想法:

  • 使用协议,这是一种有限的多重继承形式。
  • 使用成员(has-a 而不是 is-a 关系)。

我倾向于支持协议的想法。在这种情况下,DirectoryandFile将是协议,并且六个具体类将从一个公共Node超类继承并符合它们的对应协议。Node将有两个子类层次结构:一个使用 URL,一个使用路径。

现在存在对客户端代码隐藏实现的问题。为此,可以使用Node公共超类设置类集群。客户端代码将获取类型化的对象Node<File>Node<Directory>视情况而定。

任何其他/其他/类似/不同的想法?

4

4 回答 4

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

更新(基于评论)

在更一般的情况下,可能更容易为顶级“对象”使用协议,然后让每个具体实现实现该协议。您还可以使用类集群来使公共接口更干净,因此您只需拥有FileDirectory类,而不是为每种类型的后备存储提供一个。当您放弃对旧版本框架的支持时,这还允许您轻松换出实现。像这样的东西:

#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(尽管对于类集群来说这有点难看,所以这样做时要小心)。

于 2011-01-27T15:59:01.717 回答
1

如果您需要支持缺少所需方法的 NSURL 版本的系统,只需使用路径。然后,当您放弃对这些系统的支持时,转换过来,大约需要 20 分钟。如果您必须拥有这个超级复杂的系统来管理它们,那么使用 URL 所带来的效率提升几乎肯定是不值得的。

于 2011-01-27T17:09:27.587 回答
0

我不相信在这种情况下单独的子类有充分的理由只是为了注意数据的来源(URL 或路径 - 并且路径可以表示为 file:// URL)。

我的感觉是另一种模式会更适合这个。我认为这是装饰器模式 - 只需为每个文件配备一个“源”属性,在此示例中可以是 URL 或文件相关的。它在处理对象时非常方便,因为有关此属性的整个逻辑都可以放入这些辅助对象中。以后也很容易扩展。

在一般情况下,我认为协议是要走的路,但你应该总是问自己是否真的需要表达差异(这里是 URL 与文件)。通常,该代码的用户(甚至库本身)根本不应该关心。

于 2011-01-27T14:57:27.140 回答
0

您可以将 path/url 属性与节点完全分离;它们更像是节点层次结构的隐含属性,而不是节点本身,如果它们都有父节点,您可以轻松地从节点计算一个或另一个。

如果您使用不同的工厂来创建路径/url,您可以交换或扩展您的命名系统,而无需触及您的节点层次结构类。

沿着这条路径继续,如果您将所有文件操作移动到单独的类中,您的节点层次结构中将没有与版本相关的代码。

于 2011-01-27T21:04:16.737 回答