Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Class design for system which is hierarchical, but not neatly so

I've run into this several times, so would like to use a real example and get ideas on how more experienced C# developers handle this.

I'm writing a .NET wrapper around the unmanaged MediaInfo library, which gathers various data about media files (movies, images...).

MediaInfo has many functions, each applying to different types of files. For example, "PixelAspectRatio" applies to images and video but not to audio, subtitles, or others.

A subset of the functionality I'd like to wrap is below:

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

As you can see, the start of a not-too-bad class mapping would be one class for the functionality specific to each stream type and a base class with functionality common to all types.

Then this path gets a little less obvious. There are many functions common to {general, video, audio, text, and image} stream types. Okay, so I guess I can make a class with a smelly name like "GeneralVideoAudioTextImage", and then another named GeneralVideoAudioText (which inherits from GeneralVideoAudioTextImage) for the functionality common to those things, but not "Image" streams. This would awkwardly follow the "is a" rule of class hierarchies, I guess.

This is already not looking elegant, but then there are those occasional cases like "Width" which do not fit into any group which is cleanly a subset of another group. Those cases can simply duplicate functionality where necessary -- implement in Video, Text, and Image individually, but that would obviously violate DRY.

A common first approach would be MI, which C# does not support. The usual answer to that seems to be "use MI with interfaces," but I can't find entirely see how that can follow DRY. Perhaps my failing.

Class hierarchies have been discussed on SO before, as have alternatives to MI (extension methods, etc.) but none of those solutions seemed a good fit. For example, extension methods seem better used for classes whose source you cannot edit, like the String class, and are harder to locate because they are not really tied with the class, though they may work. I haven't found a question asked about a situation like this, though that's probably a failure of my use of the search tool.

An example of a MediaInfo feature, wrapped, might be:

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;
}

My question is: How have you handled situations like this, systems which are kind-of-hierarchical-but-not-quite? Have you found a reasonably elegant strategy, or just accepted that not every simple problem has one?

like image 686
Charles Burns Avatar asked Mar 16 '12 00:03

Charles Burns


1 Answers

I think you're best off using a combination of interfaces and, if the implementation is more complex than a bunch of properties, composition to provide shared implementations of the interfaces:

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.
}
like image 104
Andrew Kennan Avatar answered Sep 30 '22 06:09

Andrew Kennan