Objective-C организация иерархий кластеров классов - PullRequest
3 голосов
/ 27 января 2011

Это вопрос дизайна класса с 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

Но File и NodeWithURL не находятся в одной строке иерархии классов.Без множественного наследования нет способа выразить это в Objective-C.

Так как бы вы разработали эту ситуацию?Я вижу две идеи:

  • использовать протоколы, которые являются ограниченной формой множественного наследования.
  • использовать члены (отношения has-a вместо is-a).

Я склоняюсь к идее протокола.В этом случае Directory и File будут протоколами, а шесть конкретных классов будут наследоваться от общего суперкласса Node и соответствовать их аналогичному протоколу.Node будет иметь две иерархии подклассов: одну с использованием URL-адресов, другую с использованием путей.

Теперь существует проблема сокрытия реализации от клиентского кода.Для этого можно настроить кластер классов с помощью общего суперкласса Node.Код клиента получит объекты, набранные как Node<File> или Node<Directory> в зависимости от обстоятельств.

Какие-либо дополнительные / другие / похожие / разные идеи?

Ответы [ 4 ]

3 голосов
/ 27 января 2011

Может быть, я упускаю очевидную проблему, но ... зачем вам и 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 голос
/ 27 января 2011

Если вам нужно поддерживать системы, в которых отсутствуют версии методов, требующие NSURL, просто используйте пути.Затем, когда вы прекратите поддержку этих систем, выполните конвертацию, это займет около 20 минут.Повышение эффективности использования URL почти наверняка не стоит того, чтобы управлять этой сверхсложной системой.

0 голосов
/ 28 января 2011

Вы можете полностью отделить свойство path / url от узлов; это скорее неявное свойство иерархии узлов, чем один из самих узлов, и вы можете легко вычислить один или другой из узла, при условии, что у них всех есть родитель.

Если вы используете разные фабрики для создания вашего пути / URL, вы можете поменять или расширить свою систему именования, не затрагивая классы иерархии узлов.

Продолжая идти по этому пути, если вы переместите все файловые операции в отдельные классы, у вас не будет зависимого от версии кода в иерархии узлов.

0 голосов
/ 27 января 2011

Я не уверен, что в этом случае есть веская причина для отдельных подклассов просто отметить источник данных (URL или путь - и путь может быть выражен как file: // URL в любом случае).

Мне кажется, что другой шаблон подойдет для этого гораздо лучше.Я думаю, что это шаблон декоратора - просто снабдите каждый файл свойством «source», которое в этом примере может быть связано с URL или файлом.Это может быть очень удобно при работе с объектами, так как вся логика в отношении этого свойства может быть помещена в эти вспомогательные объекты.Это легко расширить и позже.

В общем случае я думаю, что протоколы - это путь, но вы должны всегда спрашивать себя, действительно ли вам нужно выражать разницу (здесь URL-адрес против файла).Зачастую пользователю этого кода (даже самой библиотеке) это вообще не нужно.

...