Я сталкивался с этим несколько раз, поэтому хотел бы использовать реальный пример и получить представление о том, как с этим справляются более опытные разработчики 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
Как видите, начало неплохого сопоставления классов будет состоять из одного класса для функций, специфичных для каждого типа потока, и базовый класс с функциональностью, общей для всех типов.
Затем этот путь становится немного менее очевидным. Существует много функций, общих для типов потоков {общий, видео, аудио, текст и изображение}. Итак, я думаю, я могу создать класс с вонючим именем, например «GeneralVideoAudioTextImage», а затем еще один с именем GeneralVideoAudioText (который наследуется от GeneralVideoAudioTextImage) для функций, общих для этих вещей, но не для потоков «Image». Я думаю, это будет неуклюже следовать правилу «является» иерархии классов.
Это уже не выглядит элегантно, но есть такие случайные случаи, как «Ширина», которые не вписываются ни в одну группу, являющуюся чисто подмножеством другой группы. В этих случаях можно просто дублировать функциональность, где это необходимо — реализовывать в видео, тексте и изображении по отдельности, но это, очевидно, нарушит DRY.
Распространенным первым подходом будет MI, который C# не поддерживает. Обычный ответ на это, кажется, «использовать MI с интерфейсами», но я не могу полностью понять, как это может следовать за DRY. Возможно, мой провал.
Иерархии классов уже обсуждались на SO ранее, как и альтернативыMI (методы расширения и т. д.), но ни одно из этих решений не показалось подходящим. Например, кажется, что методы расширения лучше использовать для классов, исходный код которых вы не можете редактировать, таких как класс String, и их сложнее найти, потому что они на самом деле не связаны с классом, хотя они могут работать. Я не нашел вопроса о подобной ситуации, хотя, вероятно, это связано с тем, что я не использовал инструмент поиска.
Примером обернутой функции MediaInfo может быть:
int _width = int.MinValue;
/// Width in pixels.
public int width {
get {
if(_width == int.MinValue)
_width = miGetInt("Width");
return _width;
}
}
// ... (Elsewhere, in another file) ...
/// Returns a MediaInfo value as an int, 0 if error.
/// The MediaInfo parameter.
public int miGetInt(string parameter) {
int parsedValue;
string miResult = mediaInfo.Get(streamKind, id, parameter);
int.TryParse(miResult, out parsedValue);
return parsedValue;
}
У меня такой вопрос: как вы справлялись с подобными ситуациями, когда системы являются своего рода иерархическими, но не совсем? Вы нашли достаточно элегантную стратегию или просто смирились с тем, что она есть не у каждой простой задачи?