Дизайн класса для системы, которая является иерархической, но не очень аккуратной - PullRequest
5 голосов
/ 16 марта 2012

Я сталкивался с этим несколько раз, поэтому хотел бы привести реальный пример и получить представление о том, как более опытные разработчики C # справляются с этим.

Я пишу оболочку .NET вокруг неуправляемой библиотеки MediaInfo , которая собирает различные данные о медиафайлах (фильмах, изображениях ...).

MediaInfo имеет много функций, каждая из которых применяется к различным типам файлов. Например, «PixelAspectRatio» применяется к изображениям и видео, но не к аудио, субтитрам или другим.

Ниже приведено подмножество функций, которые я хотел бы обернуть:

General Video   Audio    Text   Image  Chapters  Menu    (Name of function)    
x       x       x        x      x      x         x       Format
x       x       x        x      x      x         x       Title
x       x       x        x      x      x         x       UniqueID
x       x       x        x      x                x       CodecID
x       x       x        x      x                x       CodecID/Hint
        x       x        x      x                x       Language
x       x       x        x      x                        Encoded_Date
x       x       x        x      x                        Encoded_Library
x       x       x        x      x                        InternetMediaType
x       x       x        x      x                        StreamSize
        x       x        x      x                        BitDepth
        x       x        x      x                        Compression_Mode
        x       x        x      x                        Compression_Ratio
x       x       x        x                       x       Delay
x       x       x        x                       x       Duration
        x       x        x                               BitRate
        x       x        x                               BitRate_Mode
        x       x        x                               ChannelLayout
        x       x        x                               FrameCount
        x       x        x                               FrameRate
        x       x        x                               MuxingMode
        x       x        x                               MuxingMode
        x       x        x                               Source_Duration
        x                x      x                        Height
        x                x      x                        Width
        x                       x                        PixelAspectRatio
                x                                        SamplingRate
x                                                        Album
x                                                        AudioCount
x                                                        ChaptersCount
x                                                        EncodedBy
x                                                        Grouping
x                                                        ImageCount
x                                                        OverallBitRate
x                                                        OverallBitRate_Maximum
x                                                        OverallBitRate_Minimum
x                                                        OverallBitRate_Nominal
x                                                        TextCount
x                                                        VideoCount

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

Тогда этот путь становится немного менее очевидным. Существует много функций, общих для типов потоков {general, video, audio, text и image}. Итак, я предполагаю, что могу создать класс с вонючим именем, например «GeneralVideoAudioTextImage», а затем другой с именем GeneralVideoAudioText (который наследуется от GeneralVideoAudioTextImage) для функциональности, общей для этих вещей, но не для потоков «Image». Я полагаю, что это будет неловко следовать правилу "является" классовой иерархии.

Это уже не выглядит элегантно, но есть такие случайные случаи, как "Ширина", которые не вписываются ни в одну группу, которая является чисто подмножеством другой группы. Эти случаи могут просто дублировать функциональность в случае необходимости - реализовывать в видео, тексте и изображении по отдельности, но это, очевидно, нарушит DRY .

Распространенным первым подходом будет MI, который C # не поддерживает. Обычный ответ на это, кажется, «использовать MI с интерфейсами», но я не могу полностью понять, как это может следовать за DRY. Возможно, мой провал.

Иерархии классов обсуждались в SO ранее, как и альтернативы MI (методы расширения и т. Д.), Но ни одно из этих решений не показалось подходящим. Например, методы расширения, кажется, лучше использовать для классов, источник которых вы не можете редактировать, таких как класс String, и их сложнее найти, потому что они на самом деле не связаны с классом, хотя они могут работать. Я не нашел вопрос о такой ситуации, хотя, возможно, это сбой моего использования инструмента поиска.

Примером функции MediaInfo, обернутой, может быть:

int _width = int.MinValue;
/// <summary>Width in pixels.</summary>
public int width {
    get {
        if(_width == int.MinValue)
            _width = miGetInt("Width");
        return _width;
    }
}

// ... (Elsewhere, in another file) ...
/// <summary>Returns a MediaInfo value as an int, 0 if error.</summary>
/// <param name="parameter">The MediaInfo parameter.</param>
public int miGetInt(string parameter) {
    int parsedValue;
    string miResult = mediaInfo.Get(streamKind, id, parameter);
    int.TryParse(miResult, out parsedValue);
    return parsedValue;
}

Мой вопрос таков: как вы справлялись с подобными ситуациями, системами, которые вроде иерархические, но не совсем? Вы нашли достаточно элегантную стратегию или просто согласились, что не у каждой простой проблемы есть такая?

Ответы [ 2 ]

5 голосов
/ 16 марта 2012

Я думаю, что вам лучше использовать комбинацию интерфейсов и, если реализация более сложна, чем набор свойств, составить для обеспечения общих реализаций интерфейсов:

abstract class Media  {
 // General properties/functions
}

class VideoAndImageCommon { // Crappy name but you get the idea
 // Functions used by both video and images
}

interface IVideoAndImageCommon {
 // Common Video & Image interface
}

class Video : Media, IVideoAndImageCommon {
  private readonly VideoAndImageCommon _commonImpl = new VideoAndImageCommon();

  // Implementation of IVideoAndImageCommon delegates to _commonImpl.
}

class Image : Media, IVideoAndImageCommon {
  private readonly VideoAndImageCommon _commonImpl = new VideoAndImageCommon();

  // Implementation of IVideoAndImageCommon delegates to _commonImpl.
}
0 голосов
/ 19 марта 2012

Я отметил правильный ответ Эндрю Кеннана, потому что я думаю, что это, вероятно, лучший общий подход к несколько сложной иерархии.Тем не менее, я не использовал его предложение в своем окончательном коде.

Я публикую этот новый ответ, чтобы помочь любым будущим пользователям SO, которые ищут этот вопрос из-за схожей проблемы.

Хотя интерфейсы, вероятно, являются лучшим общим решением для чего-то более чисто иерархического, в этом случае они мне не подошли.Я не смог бы выпустить это как проект OSS без использования анонимного имени, если бы я сделал иерархическую группировку.Предполагаемые имена интерфейсов начали пахнуть очень плохо, например, «IGeneralVideoAudioTextImageMenuCommon» и «IVideoAudioTextImageMenuCommon»;декларации о наследовании с 4 или 5 из них были не элегантными, подобных тем, которые человечество ранее не видело:

    ///<summary>Represents a single video stream.</summary>
    public sealed class VideoStream : Media, IGeneralVideoAudioTextImageMenuCommon,
    IGeneralVideoAudioTextMenuCommon, IVideoAudioTextImageMenuCommon,
    IVideoTextImageCommon, /* ...ad nauseum. */
    {
        // What would you even name these variables?
        GeneralVideoAudioTextImageMenuCommon gvatimCommon;
        GeneralVideoAudioTextMenuCommon gvatmCommon;
        VideoAudioTextImageMenuCommon vatimCommon;
        VideoTextImageCommon vticCommon;

        public VideoStream(MediaInfo mi, int id) {
        gvatimCommon = new GeneralVideoAudioTextImageMenuCommon(mi, id);
        gvatmCommon = new GeneralVideoAudioTextMenuCommon(mi, id);
        vatimCommon = new VideoAudioTextImageMenuCommon(mi, id);
        vticCommon = new VideoTextImageCommon(mi, id);
        // --- and more. There are so far at least 10 groupings. 10!
        /* more code */
    }

Именование одна из двух трудных проблем в информатике в соответствии сФил Карлтон действительно приближается к более философскому определению «это».Автомобиль "является" транспортным средством, и да, технически автомобиль также "является" MotorcycleCarTruckOrAirplane, но это все о том, чтобы позволить людям понять код, верно?

Я рассматривал гибридный подход, где группировки сНебольшой набор функций будет объединен.Но потом я спросил себя: "Это облегчает понимание кода?"Мой ответ был «Нет», потому что просмотр иерархии наследования подразумевает определенную организацию кода для читателя.Второй метод, вероятно, добавил бы больше сложности, чем было сохранено: не выигрыш.

Мое решение, хотя я все еще считаю, что лучший вариант должен существовать, состоит в том, чтобы поместить все функции, общие для нескольких потоков, в один класс., «MultiStreamCommon», внутренне сгруппированный с использованием #regions:

public class MultiStreamCommon : Media
{
    public MultiStreamCommon(MediaInfo mediaInfo, StreamKind kind, int id)
        : base(mediaInfo, id) {
            this.kind = kind;
    }

    #region AllStreamsCommon
    string _format;
    ///<summary>The format or container of this file or stream.</summary>
    ///<example>Windows Media, JPEG, MPEG-4.</example>
    public string format { /* implementation */ };

    string _title;
    ///<summary>The title of the movie, track, song, etc..</summary>
    public string title { /* implementation */ };

    /* more accessors */
    #endregion

    #region VideoAudioTextCommon
    /* Methods appropriate to this region. */
    #endregion
    // More regions, one for each grouping.
}

Каждый поток создает экземпляр MultiStreamCommon и предоставляет соответствующие функции через средства доступа:

public sealed class VideoStream : Media
{
    readonly MultiStreamCommon streamCommon;

    ///<summary>VideoStream constructor</summary>
    ///<param name="mediaInfo">A MediaInfo object.</param>
    ///<param name="id">The ID for this audio stream.</param>
    public VideoStream(MediaInfo mediaInfo, int id) : base(mediaInfo, id) {
        this.kind = StreamKind.Video;
        streamCommon = new MultiStreamCommon(mediaInfo, kind, id);
    }

    public string format { get { return streamCommon.format; } }
    public string title { get { return streamCommon.title; } }
    public string uniqueId { get { return streamCommon.uniqueId; } }
    /* ...One line for every media function relevant to this stream type */
}

Преимущество состоит в том, что уродство,в то время как все еще там, ограничен классом MultiStreamCommon.Я считаю, что группировки #region гораздо более читабельны, чем каждый потоковый класс, имеющий шесть или около того унаследованных интерфейсов.Как сопровождающий узнает, куда добавить новую функцию MediaInfo?Просто выясните, к каким потокам это относится и поместите его в соответствующий регион.Если они неправильно сгруппированы, это все еще работает и может быть замечено через средство доступа.

Недостатком является то, что медиапотоки не наследуют соответствующую функциональность - средства доступа должны быть записаны.Документация также не наследуется, поэтому каждому потоку понадобится тег для каждого средства доступа, что приведет к дублированию документации.Тем не менее: Дублированная документация лучше, чем дублированный код.

Обеспечение правильной иерархии наследования в этом случае намного сложнее, чем основная проблема, о которой мы должны помнить, это просто обертывание некоторыхlibrary.

Для тех, кто заинтересован в коде, связанном с этим потоком, или в его назначении (простое получение информации о файлах мультимедиа с помощью .NET), кодовая страница Google для этого проекта: здесь .Пожалуйста, дайте мне знать о любых мыслях, особенно от тех, кто опубликовал большое количество прекрасного кода, с которым я столкнулся (Джон Скит, Марк Гравелл и другие).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...