3

これは、Objective-C を使用したクラス設計の問題です。以下に例を示します。

ファイル システムには、ファイルとディレクトリがあります。どちらも「ノード」です。たとえば、ディレクトリをたどると、ノードのリストが生成されます。一部は [サブ] ディレクトリで、他はファイルです。

これは、クラス階層の次のクライアント側抽象ビューを指します。

@interface Node: NSObject {}
@end

@interface Directory: Node {}
@end

@interface File: Node {}
@end

ここまでは順調ですね。この時点で、3 つのクラスはすべて抽象クラスです。実装に進むと、2 つの主要なルートがあることがわかります: URL を使用する (Mac OS X ≥ 10.6 では Apple が推奨)、またはパス (Mac OS X ≤ 10.5 またはCocotronでのみ可能な方法)。

したがって、上記の 3 つの抽象クラスのそれぞれの 2 つの具体的な実装を開発する必要があります。

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

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

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

ここで、次のように考えてFileWithURLください。

  • ファイルなので、から継承する必要がありますFile
  • URLで実装されたノードなので、から継承する必要がありますNodeWithURL

しかしFile、 とNodeWithURLは同じクラス階層ライン内にはありません。多重継承がなければ、Objective-C でそれを表現する方法はありません。

では、この状況をどのように設計しますか? 私は2つのアイデアを見ることができます:

  • 多重継承の制限された形式であるプロトコルを使用します。
  • メンバーを使用します (is-a 関係ではなく has-a 関係)。

私はプロトコルのアイデアを好む傾向があります。その場合、DirectoryFileはプロトコルになり、6 つの具体的なクラスは共通のスーパークラスから継承されNode、対応するプロトコルに準拠します。Node1 つは URL を使用し、もう 1 つはパスを使用します。

ここで、実装をクライアント コードから隠すという問題があります。Nodeクラス クラスタは、共通のスーパークラスを使用してこの目的のために設定できます。Node<File>クライアント コードは、場合に応じて型付けされたオブジェクトを取得しますNode<Directory>

追加/その他/類似/異なるアイデアはありますか?

4

4 に答える 4

3

明らかな問題を見落としているのかもしれませんが、なぜオブジェクトの URL とパスの実装の両方が必要なのですか? パスを URL として保存し、必要に応じて 2 つの間で変換できるようです。クラスの合理的な実装は次のようになります。

@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

更新(コメントに基づく)

より一般的なケースでは、最上位の「オブジェクト」にプロトコルを使用し、具体的な実装ごとにプロトコルを実装する方が簡単な場合があります。また、クラス クラスターを使用してパブリック インターフェイスをよりクリーンにすることもできます。これにより、バッキング ストアの種類ごとに 1 つではなく、クラス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(ただし、クラス クラスタでは見苦しくなりますので、その場合は注意してください)。

于 2011-01-27T15:59:01.717 に答える
1

必要なメソッドの NSURL 取得バージョンがないシステムをサポートする必要がある場合は、パスを使用してください。その後、これらのシステムのサポートを終了して変換すると、20 分ほどかかります。URL を管理するためにこの非常に複雑なシステムを使用する必要がある場合、URL を使用することによる効率の向上はほぼ確実に価値がありません。

于 2011-01-27T17:09:27.587 に答える
0

path/url プロパティをノードから完全に切り離すことができます。これらは、ノード自体の 1 つというよりは、ノード階層の暗黙的なプロパティであり、ノードからいずれかを簡単に計算できます。ただし、すべてのノードに親があればです。

パス/URL の作成に異なるファクトリを使用する場合は、ノード階層クラスに触れることなく、ネーミング システムを交換または拡張できます。

このパスを続けて、すべてのファイル操作を別のクラスに移動すると、Node 階層にバージョン依存のコードがなくなります。

于 2011-01-27T21:04:16.737 に答える
0

この場合、データの出所 (URL またはパス - とにかくパスは file:// URL として表現できます) に注意するためだけに、個別のサブクラスに十分な理由があるとは確信していません。

私の感覚では、別のパターンの方がこれに適していると思います。これはデコレータ パターンだと思います。この例では、すべてのファイルに「ソース」プロパティを設定するだけです。これは、この例では URL またはファイルに関連しています。このプロパティに関するロジック全体をこれらのヘルパー オブジェクトに入れることができるため、オブジェクトを操作するときに非常に便利です。あとから伸ばすのも簡単です。

一般的なケースでは、プロトコルの方が適していると思いますが、違い (ここでは URL とファイル) を本当に表現する必要があるかどうかを常に自問する必要があります。多くの場合、そのコードのユーザー (ライブラリ自体でさえも) はまったく気にする必要はありません。

于 2011-01-27T14:57:27.140 に答える