2012-03-16 37 views
5

Tôi đã chạy vào nhiều lần này, vì vậy muốn sử dụng một ví dụ thực tế và lấy ý tưởng về cách các nhà phát triển C# có kinh nghiệm xử lý việc này.Thiết kế lớp cho hệ thống phân cấp, nhưng không gọn gàng vì vậy

Tôi đang viết một trình bao bọc .NET xung quanh thư viện MediaInfo không được quản lý, tập hợp dữ liệu khác nhau về tệp phương tiện (phim, hình ảnh ...).

MediaInfo có nhiều chức năng, mỗi chức năng áp dụng cho các loại tệp khác nhau. Ví dụ: "PixelAspectRatio" áp dụng cho hình ảnh và video nhưng không áp dụng cho âm thanh, phụ đề hoặc video khác.

Một tập hợp các chức năng tôi muốn quấn dưới:

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 

Như bạn có thể thấy, sự bắt đầu của một ánh xạ lớp không-quá-xấu sẽ là một lớp cho các chức năng cụ thể để mỗi loại luồng và một lớp cơ sở có chức năng chung cho tất cả các loại.

Sau đó, đường dẫn này ít rõ ràng hơn một chút. Có nhiều chức năng phổ biến cho các loại luồng {chung, video, âm thanh, văn bản và hình ảnh}. Được rồi, vì vậy tôi đoán tôi có thể tạo một lớp với tên có mùi như "GeneralVideoAudioTextImage" và một tên khác có tên GeneralVideoAudioText (kế thừa từ GeneralVideoAudioTextImage) cho chức năng phổ biến cho những thứ đó chứ không phải luồng "Hình ảnh". Điều này sẽ lúng túng theo nguyên tắc "là một" của phân cấp lớp, tôi đoán vậy.

Điều này đã không được tìm kiếm thanh lịch, nhưng sau đó có những trường hợp không thường xuyên như "Chiều rộng" không phù hợp với bất kỳ nhóm nào là sạch một tập hợp con của một nhóm khác. Những trường hợp đó có thể chỉ đơn giản là trùng lặp chức năng khi cần thiết - triển khai trong Video, Văn bản và Hình ảnh riêng lẻ, nhưng điều đó rõ ràng sẽ vi phạm DRY.

Cách tiếp cận đầu tiên phổ biến là MI, C# không hỗ trợ. Câu trả lời thông thường cho rằng dường như là "sử dụng MI với giao diện", nhưng tôi không thể tìm thấy hoàn toàn xem làm thế nào mà có thể làm theo DRY. Có lẽ thất bại của tôi.

Phân cấp lớp đã được thảo luận trên SO trước đây, vì có alternatives đến MI (phương pháp mở rộng, v.v.) nhưng không có giải pháp nào phù hợp. Ví dụ, các phương thức mở rộng có vẻ tốt hơn được sử dụng cho các lớp có nguồn mà bạn không thể chỉnh sửa, như lớp String và khó định vị hơn vì chúng không thực sự gắn với lớp, mặc dù chúng có thể hoạt động. Tôi chưa tìm thấy câu hỏi nào về tình huống như thế này, mặc dù đó có thể là sự thất bại trong việc sử dụng công cụ tìm kiếm của tôi.

Một ví dụ về một tính năng MediaInfo, bọc, có thể là:

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

Câu hỏi của tôi là: Làm thế nào bạn đã xử lý tình huống như thế này, các hệ thống đó là loại-of-thứ bậc-nhưng-không-khá? Bạn đã tìm thấy một chiến lược hợp lý thanh lịch, hay chỉ chấp nhận rằng không phải mọi vấn đề đơn giản đều có một?

+0

Nếu bạn không tìm thấy giải pháp nào tốt hơn, có thể codegen sử dụng CodeDOM hoặc Roslyn có thể hữu ích. – svick

Trả lời

5

Tôi nghĩ rằng bạn tốt nhất nên sử dụng kết hợp các giao diện, và nếu thực hiện phức tạp hơn một loạt các tính chất, thành phần để cung cấp triển khai chia sẻ các giao diện:

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

tôi đánh dấu Andrew Kennan của trả lời đúng vì tôi nghĩ rằng đó có lẽ là cách tiếp cận chung nhất chung cho một hệ thống phân cấp hơi phức tạp. Tuy nhiên, tôi đã không sử dụng đề xuất của anh ấy trong mã cuối cùng của tôi.

Tôi đang đăng câu trả lời mới này để giúp bất kỳ người dùng SO nào trong tương lai tra cứu câu hỏi này vì có vấn đề tương tự.

Mặc dù giao diện có lẽ là giải pháp chung tốt nhất cho thứ gì đó phân cấp rõ ràng hơn nhưng chúng không hoạt động đối với tôi trong trường hợp này. Tôi sẽ không thể phát hành điều này như là một dự án PMNM mà không sử dụng một tên vô danh mà tôi đã thực hiện phân nhóm theo cấp bậc. Tên giao diện ngụ ý bắt đầu có mùi rất tệ, như "IGeneralVideoAudioTextImageMenuCommon" và "IVideoAudioTextImageMenuCommon"; tờ khai thừa kế với 4 hoặc 5 trong số này là thanh nha thích của mà nhân loại chưa từng thấy:

///<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 */ 
    } 

Việc đặt tên, một trong hai vấn đề khó khăn trong khoa học máy tính theo Phil Karlton, thực sự tiếp cận một định nghĩa triết học hơn về "là một". Một chiếc xe "là một" chiếc xe, và có kỹ thuật một chiếc xe cũng "là một" MotorcycleCarTruckOrAirplane, nhưng điều này là tất cả về cho phép con người hiểu mã, phải không?

Tôi đã xem xét phương pháp kết hợp, nơi các nhóm có một tập hợp chức năng nhỏ sẽ được hợp nhất. Nhưng sau đó tôi tự hỏi, "điều này làm cho mã dễ hiểu hơn?" Câu trả lời của tôi là, "Không", bởi vì nhìn thấy một hệ thống phân cấp thừa kế ngụ ý một tổ chức mã nhất định cho người đọc. Một phương pháp thứ hai có khả năng sẽ thêm phức tạp hơn so với đã được lưu: Không phải là một chiến thắng.

Giải pháp của tôi, mặc dù tôi vẫn tin một tốt hơn phải tồn tại, là để đặt tất cả các chức năng chung cho nhiều hơn một dòng vào một lớp duy nhất, "MultiStreamCommon", nhóm nội bộ sử dụng #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. 
} 

mỗi dòng tạo ra một thể hiện của MultiStreamCommon và thấy nhiều chức năng liên quan thông qua accessors:

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 */ 
} 

ngược ở đây là sự xấu xí, trong khi vẫn còn đó, được giới hạn ở những lớp MultiStreamCommon. Tôi tin rằng các nhóm #region dễ đọc hơn nhiều so với mỗi lớp luồng có sáu giao diện kế thừa. Làm thế nào để một người duy trì biết nơi để thêm một chức năng MediaInfo mới? Chỉ cần tìm ra các luồng mà nó áp dụng và đặt nó vào khu vực thích hợp. Nếu họ misgroup, nó vẫn hoạt động và có thể được nhìn thấy thông qua một accessor.

Nhược điểm là luồng phương tiện truyền thông không kế thừa chức năng thích hợp - người truy cập phải được viết. Tài liệu cũng không được kế thừa, do đó mỗi luồng sẽ cần một thẻ cho mọi người truy cập, dẫn đến tài liệu trùng lặp. Điều đó cho biết: Tài liệu trùng lặp tốt hơn so với mã trùng lặp.

Việc thực thi phân cấp thừa kế phù hợp trong trường hợp này phức tạp hơn nhiều so với vấn đề cơ bản, mà chúng ta phải nhớ, chỉ gói một số thư viện.

Đối với những người quan tâm đến mã được liên kết với chủ đề này hoặc trong mục đích của nó (dễ dàng nhận thông tin tệp phương tiện bằng .NET), trang Mã Google cho dự án này là here. Xin vui lòng cho tôi biết bất kỳ suy nghĩ, đặc biệt là từ những người đã đăng một số lượng lớn các mã đẹp tôi đã đi qua (Jon Skeet, Marc Gravell, và những người khác).

Các vấn đề liên quan