2015-02-18 13 views
6

Tôi đang cố gắng tìm một mẫu thiết kế đẹp, sạch hoặc thường được chấp nhận để xử lý một kiểu liệt kê loại cá nhân chỉ được biết khi chạy.Mẫu thiết kế hoặc các giải pháp được chấp nhận để tránh chuyển đổi trên các loại

Tôi biết câu hỏi tương tự đã được hỏi trước đó, nhưng vẫn không rõ ràng với tôi rằng các triển khai thay thế có lợi thế đáng kể so với một công tắc hoặc một loạt các lỗi.

Trước tiên, tôi sẽ trình bày một vài triển khai, sau đó tôi sẽ đặt câu hỏi: Các triển khai này có tốt hơn hoặc được ưu tiên hơn chuyển đổi đơn giản không? Nếu vậy, tại sao? Nếu không, tai sao không?

Trong đơn đăng ký của tôi, tôi gửi và nhận dữ liệu qua luồng. Vào thời gian chạy, tôi nhận được một cấu trúc dữ liệu qua serialization mô tả những trường nào được đặt trong dữ liệu nhị phân của tôi. Điều này bao gồm Loại dữ liệu trong trường, tức là Int32, Bool, Double, vv Tại thời điểm thiết kế, tất cả những gì tôi biết là dữ liệu có thể thuộc một trong nhiều loại. Tôi cần phải đọc các trường từ luồng và xử lý dữ liệu một cách thích hợp.

Nếu chuyển trên các loại được phép, một giải pháp có thể như sau:

Non-Working Code:

object ReadDataField(byte [] buff, ref int position, 
    Dictionary<int, Type> fields) 
{ 
    object value; 
    int field = buff[position]; 
    position++; 

    switch(fields[field]) 
    { 
     case typeof(Int32): 
     { 
      value = (Int32)BitConverter.ToInt32(buff, position); 
      position += sizeof(Int32); 
      break; 
     } 
     case typeof(Int16): 
     { 
      value = (Int16)BitConverter.ToInt16(buff, position); 
      position += sizeof(Int16); 
      break; 
     } 
     // Etc... 
    } 

    return value; 
} 

Theo tôi, mã này có lợi thế là đơn giản , dễ đọc và dễ bảo trì.

Tuy nhiên, khi chuyển đổi trên các loại không có sẵn trong C# Tôi thực hiện ở trên như sau:

Mã làm việc:

enum RawDataTypes 
{ 
    Int32, 
    Int16, 
    Double, 
    Single, 
    etc. 
} 

object ReadDataField(byte [] buff, ref int position, 
    Dictionary<int, RawDataTypes> fields) 
{ 
    object value; 
    int field = buff[position]; 
    position++; 

    switch(fields[field]) 
    { 
     case RawDataTypes.Int32: 
     { 
      value = (int)BitConverter.ToInt32(buff, position); 
      position += sizeof(int); 
      break; 
     } 
     case RawDataTypes.Int16: 
     { 
      value = (Int16)BitConverter.ToInt16(buff, position); 
      position += sizeof(Int16); 
      break; 
     } 
     // Etc. 
    } 

    return value; 
} 

Đây rõ ràng là một công việc xung quanh, nhưng nó cũng là đơn giản và dễ bảo trì.

Tuy nhiên, có một số bài viết chi tiết về chuyển đổi trên các loại không có sẵn trong C#. Và bên cạnh những khó khăn đối phó với thừa kế theo cách mà mang lại một kết quả mong đợi, vv, tôi đã nhìn thấy nhiều câu trả lời đã nói có một cách tiếp cận "tốt hơn" nhiều hơn phù hợp với tinh thần của lập trình hướng đối tượng.

Các giải pháp phổ biến được đề xuất là 1) sử dụng đa hình hoặc 2) sử dụng tra cứu từ điển. Nhưng việc thực hiện hoặc có những thách thức riêng.

Về đa hình, sau đây là một ví dụ về "nó sẽ không được tốt đẹp nếu nó làm việc" mã:

Non-Working Thực hiện Polymorphism:

object ReadDataField(byte [] buff, int position, 
    Dictionary<int, Type> fields) 
{ 
    int field = buff[position]; 
    position++; 

    object value = Activator.CreateInstance(fields[field]); 
    // Here we're trying to use an extension method on the raw data type. 
    value.ReadRawData(buff, ref position); 

    return value; 
} 

public static Int32 ReadRawData(this Int32 value, byte[] buff, ref int position) 
{ 
    value = BitConverter.ToInt32(buff, position); 
    position += sizeof(Int32); 

    return value; 
} 

public static Int16 ReadRawData(this Int16 value, byte[] buff, ref int position) 
{ 
    value = BitConverter.ToInt16 (buff, position); 
    position += sizeof(Int16); 

    return value; 
} 

// Additional methods for each type... 

Nếu bạn cố gắng biên dịch mã ở trên bạn sẽ nhận được:

'đối tượng' không chứa định nghĩa cho 'ReadRawData' và quá tải phương pháp mở rộng tốt nhất 'RawDataFieldExt ensions.ReadRawData (ngắn, byte [], ref int) 'có một số đối số không hợp lệ trong blah blah ...

Bạn không thể phân lớp các loại dữ liệu thô để thêm chức năng, bởi vì chúng được niêm phong, vì vậy các phương pháp mở rộng có vẻ như một tùy chọn. Tuy nhiên, các phương thức mở rộng sẽ không chuyển đổi từ 'object' sang kiểu thực tế, mặc dù gọi value.GetType() trả về kiểu cơ bản: System.Int32, System.Int16, etc. Sử dụng từ khóa 'dynamic' không trợ giúp, vì, vì you can't use extension methods on a dynamic type.

Trên đây thể được thực hiện để làm việc bằng cách thông qua một thể hiện của đối tượng chính nó như là một tham số để phương pháp với các thông số đa hình:

Thực hiện làm việc của Polymorphism:

object ReadDataField(byte [] buff, int position, 
    Dictionary<int, Type> fields) 
{ 
    int field = buff[position]; 
    position++; 

    dynamic value = Activator.CreateInstance(fields[field]); 
    // Here the object is passed to an overloaded method. 
    value = ReadRawData(value, buff, ref position); 

    return value; 
} 

public static Int32 ReadRawData(Int32 value, byte[] buff, ref int position) 
{ 
    value = BitConverter.ToInt32(buff, position); 
    position += sizeof(Int32); 

    return value; 
} 

public static Int16 ReadRawData(Int16 value, byte[] buff, ref int position) 
{ 
    value = BitConverter.ToInt16 (buff, position); 
    position += sizeof(Int16); 

    return value; 
} 

// Additional methods for each type... 

Trên đây mã hoạt động và vẫn còn đơn giản và có thể duy trì, và có lẽ nhiều hơn "theo tinh thần lập trình hướng đối tượng."

Nhưng thực sự có "tốt hơn không?" Tôi sẽ cho rằng nó làm cho nó thêm nhiều hơn khó duy trì, vì nó đòi hỏi nhiều tìm kiếm hơn để xem loại nào đã được triển khai.

Phương pháp thay thế là sử dụng tra cứu từ điển. mã như vậy có thể trông như thế này:

điển Thực hiện:

delegate object ReadDelegate(byte [] buff, ref int position); 

static Dictionary<Type, ReadDelegate> readers = new Dictionary<Type, ReadDelegate> 
{ 
    { typeof(Int32), ReadInt32 }, 
    { typeof(Int16), ReadInt16 }, 
    // Etc... 
}; 

object ReadDataField(byte [] buff, int position, 
    Dictionary<int, Type> fields) 
{ 
    int field = buff[position]; 
    position++; 

    object value = readers[fields[field]](buff, ref position); 

    return value; 
} 

public static object ReadInt32(byte[] buff, ref int position) 
{ 
    Int32 value = BitConverter.ToInt32(buff, position); 
    position += sizeof(Int32); 

    return value; 
} 

public static object ReadInt16(byte[] buff, ref int position) 
{ 
    return BitConverter.ToInt16(buff, position); 
    position += sizeof(Int16); 

    return value; 
} 

// Additional methods for each type... 

Một lợi thế của việc thực hiện từ điển, theo ý kiến ​​của tôi, so với các giải pháp đa hình là nó liệt kê tất cả các loại có thể được xử lý ở một nơi dễ đọc. Điều này rất hữu ích cho việc bảo trì.

Tuy nhiên, với những ví dụ này, có triển khai nào tốt hơn, sạch hơn, được chấp nhận hơn, v.v. có lợi thế đáng kể so với trên không? Các triển khai này có sử dụng tính đa hình hoặc tra cứu từ điển được ưu tiên hơn bằng cách sử dụng nút chuyển không? Tôi không thực sự tiết kiệm bất kỳ mã nào, và tôi không chắc rằng tôi đã tăng khả năng bảo trì của mã.

Trong mọi trường hợp, tôi vẫn cần phải liệt kê từng loại với phương pháp riêng của nó. Đa hình là trì hoãn điều kiện cho chính ngôn ngữ đó, chứ không phải là rõ ràng với một công tắc hoặc nếu-thì. Sử dụng một từ điển là dựa vào các điều kiện nội bộ để thực hiện tra cứu riêng của nó. Vào cuối ngày, sự khác biệt là gì?

+0

Hút thuốc lá, đó là câu hỏi được viết rộng rãi. Có, đa hình là cách thông thường để tránh phải bật các loại, kiểm tra các loại cụ thể, v.v. Vui lòng thu hẹp câu hỏi để có thể trả lời một cách cụ thể, rõ ràng. Xem http://stackoverflow.com/help/how-to-ask –

+2

Tôi thích câu hỏi được kể lại như những câu chuyện :) Nó trình bày toàn bộ quá trình suy nghĩ và dẫn dắt tất cả các khả năng. Chỉ là suy nghĩ của tôi. – Fka

+0

@PeterDuniho, tôi viết lại câu hỏi của mình để thu hẹp nó xuống một chút. Tuy nhiên, tôi muốn kết hợp một số ví dụ triển khai và suy nghĩ đằng sau chúng. – tfjield

Trả lời

3
  1. Sử dụng bộ sưu tập tĩnh 'Trình biến đổi' (hoặc bất kỳ thứ gì) mà tất cả đều triển khai một giao diện chung. Sau đó, bạn có thể lặp qua bộ sưu tập đó yêu cầu mỗi bộ sưu tập nếu họ xử lý loại. Nếu có, hãy yêu cầu họ làm điều đó. Mỗi bộ chuyển đổi chỉ biết về loại của nó.
  2. Sử dụng cùng một bộ sưu tập 'bộ sưu tập' tĩnh của bộ chuyển đổi nhưng giữ chúng trong Từ điển được khóa theo loại. Sau đó yêu cầu trình chuyển đổi theo loại từ điển và yêu cầu nó chuyển đổi cho bạn.
+2

Tôi thích 2. Các phần tử thu thập có thể là các đại biểu, được định nghĩa trên bay vì chúng là một lớp lót (về cơ bản là các cuộc gọi Chuyển đổi) .-- Thường thì câu trả lời cho một công tắc là một Từ điển được chỉ mục theo tiêu chí chuyển đổi. Nó giảm tải độ phức tạp từ mã xuống dữ liệu. Cho dù đó là dễ đọc hơn hoặc duy trì được mở cho cuộc tranh luận - có rất ít điều dễ hiểu hơn và duy trì hơn một chuyển đổi trường học cũ tốt đẹp. Nhưng ngay sau khi bạn lập trình chuyển đổi * giây * ở đâu đó thì đó là lúc để lưu trữ thông tin trong dữ liệu. –

+0

Có, một đại biểu chứ không phải là một ví dụ của một 'công cụ chuyển đổi' là một ý tưởng thú vị mà tôi không cân nhắc. Đây là thứ tôi có thể sử dụng ở nơi khác. Cảm ơn ý tưởng @Peter Schneider. –

+0

Cảm ơn bạn đã nhập, các bạn. Theo ước tính tốt nhất của tôi, tôi đã kết hợp ý tưởng của bạn vào câu hỏi của tôi ở trên. Trước tiên, tôi đang chụp những gì bạn đang đề xuất? Và thứ hai, nếu tôi, theo ý kiến ​​của bạn, điều này tốt hơn điều kiện đơn giản? @PeterSchneider, nhận xét của bạn về khả năng đọc và khả năng bảo trì của switch_ và "ngay sau khi bạn lập trình chuyển đổi _second_ ở đâu đó là lúc lưu trữ thông tin trong dữ liệu" là khiến tôi nghĩ! :) – tfjield

0

Sử dụng các mẫu thiết kế Strategy:

Xác định đối tượng chuyển đổi riêng biệt (với một giao diện chung) mà gói gọn các thuật toán chuyển đổi khác nhau. Khách hàng ủy quyền chuyển đổi thành đối tượng chuyển đổi thích hợp tại thời gian chạy.

Điều này làm giảm đáng kể phụ thuộc triển khai. Mã máy khách là độc lập với cách chuyển đổi được triển khai.

Tôi đồng ý với câu trả lời của @David Osborne. Và ngay cả khi bạn thực hiện một đối tượng chuyển đổi đơn lẻ với lệnh chuyển đổi, việc triển khai này được đóng gói và ẩn khỏi máy khách.

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