2012-02-29 26 views
10

Tôi đang cố gắng tìm một cách tốt hơn để xử lý một số cấu trúc ngày càng tăng if để xử lý các lớp khác nhau. Các lớp này, cuối cùng, các trình bao bọc xung quanh các loại giá trị khác nhau (int, DateTime, vv) với một số thông tin trạng thái bổ sung. Vì vậy, sự khác biệt chính giữa các lớp này là loại dữ liệu mà chúng chứa. Trong khi chúng thực hiện các giao diện chung, chúng cũng cần được giữ trong các bộ sưu tập đồng nhất, do đó chúng cũng thực hiện một giao diện không chung chung. Các cá thể lớp được xử lý theo loại dữ liệu mà chúng đại diện và sự thúc đẩy của chúng tiếp tục hoặc không tiếp tục dựa trên đó.Đôi công văn và lựa chọn thay thế

Mặc dù điều này không nhất thiết là vấn đề .NET hoặc C#, mã của tôi nằm trong C#.

lớp Ví dụ:

interface ITimedValue { 
TimeSpan TimeStamp { get; } 
} 

interface ITimedValue<T> : ITimedValue { 
T Value { get; } 
} 

class NumericValue : ITimedValue<float> { 
public TimeSpan TimeStamp { get; private set; } 
public float Value { get; private set; } 
} 

class DateTimeValue : ITimedValue<DateTime> { 
public TimeSpan TimeStamp { get; private set; } 
public DateTime Value { get; private set; } 
} 

class NumericEvaluator { 
public void Evaluate(IEnumerable<ITimedValue> values) ... 
} 

tôi đã đưa ra hai lựa chọn:

đúp văn

Gần đây tôi đã học được của mô hình khách và sử dụng các công văn đúp để xử lý chỉ Trường hợp như vậy. Điều này hấp dẫn vì nó sẽ cho phép dữ liệu không mong muốn không được đẩy (nếu chúng ta chỉ muốn xử lý một int, chúng ta có thể xử lý nó khác với một DateTime). Ngoài ra, các hành vi về cách xử lý các loại khác nhau sẽ được giới hạn trong một lớp duy nhất đang xử lý công văn. Nhưng có một chút bảo trì công bằng nếu/khi một loại giá trị mới phải được hỗ trợ.

Liên minh Lớp

Một lớp học có chứa một tài sản đối với từng loại giá trị được hỗ trợ có thể là những gì mỗi người trong các cửa hàng các lớp học. Bất kỳ hoạt động nào trên một giá trị sẽ ảnh hưởng đến thành phần thích hợp. Điều này ít phức tạp hơn và ít bảo trì hơn so với chiến lược điều phối kép, nhưng điều đó có nghĩa là mọi phần dữ liệu sẽ truyền đi tất cả các cách không cần thiết vì bạn không thể phân biệt đối xử dọc theo dòng "Tôi không hoạt động theo kiểu dữ liệu đó ". Tuy nhiên, nếu/khi các kiểu mới cần được hỗ trợ, chúng chỉ cần đi vào lớp này (cộng với bất kỳ lớp bổ sung nào cần được tạo để hỗ trợ kiểu dữ liệu mới).

class UnionData { 
public int NumericValue; 
public DateTime DateTimeValue; 
} 

Có các tùy chọn nào tốt hơn không? Có một cái gì đó trong một trong hai lựa chọn mà tôi đã không xem xét rằng tôi nên?

+0

Điều gì có thể xảy ra trong phương thức 'Đánh giá' của' NumericEvaluator' sẽ hoạt động trên 'DateTime' * hoặc * một' float'? –

+0

trên thiết bị di động ngay bây giờ, vì vậy tôi không thể viết câu trả lời thích hợp, nhưng hãy thử sử dụng tính năng động cho công văn kép (làm giảm đáng kể bản mẫu yêu cầu của mẫu khách truy cập) hoặc triển khai các loại công đoàn trong C# (Tôi nhớ một thực hiện đẹp bởi @ Juliet một nơi nào đó ở đây trên SO) –

+0

@ Chris Shain: Đó là một phần của lợi ích của giải pháp hai công văn - nó sẽ không phải. Đối với một 'DateTime', một trong hai giải pháp công văn bỏ qua nó hoặc giải pháp biến thể tiêu thụ nó như là một giá trị 0. – redman

Trả lời

3

phương pháp 1, sử dụng động cho công văn kép (tín dụng đi đến http://blogs.msdn.com/b/curth/archive/2008/11/15/c-dynamic-and-multiple-dispatch.aspx). Về cơ bản bạn có thể đã truy cập mẫu của bạn đơn giản như thế này:

class Evaluator { 
public void Evaluate(IEnumerable<ITimedValue> values) { 
    foreach(var v in values) 
    { 
     Eval((dynamic)(v)); 
    } 
} 

private void Eval(DateTimeValue d) { 
    Console.WriteLine(d.Value.ToString() + " is a datetime"); 
} 

private void Eval(NumericValue f) { 
    Console.WriteLine(f.Value.ToString() + " is a float"); 
} 

} 

mẫu sử dụng:

var l = new List<ITimedValue>(){ 
    new NumericValue(){Value= 5.1F}, 
    new DateTimeValue() {Value= DateTime.Now}}; 

new Evaluator() 
    .Evaluate(l); 
     // output: 
     // 5,1 is a float 
     // 29/02/2012 19:15:16 is a datetime 

phương pháp 2 sẽ sử dụng các loại Liên minh trong C# như đề xuất của @Juliet here (thực hiện thay thế here)

+0

Rất vui khi bạn quay lại và đăng những nội dung này, tôi không thể tìm thấy chúng. Và cảm ơn bạn đã sửa lỗi việc sử dụng cụm từ "biến thể" không chính xác của tôi. – redman

+0

Tôi đã nhìn thấy chúng được gọi là các loại biến thể quá. Và tổng hợp các loại và (có thể không chính xác?) Các loại dữ liệu đại số. Tôi không phải là một chuyên gia vì vậy tôi đã không cố gắng để sửa chữa bạn: "các loại công đoàn" chỉ xảy ra là cách họ được gọi là trong câu trả lời tôi đã cố gắng để chỉ cho bạn. :) –

0

Tôi nói với bạn rằng tôi đã giải quyết một tình huống tương tự - bằng cách lưu trữ Ticks của một DateTime hoặc TimeSpan làm gấp đôi trong bộ sưu tập và bằng cách sử dụng IComparable làm hạn chế về tham số kiểu. Việc chuyển đổi thành double/from double được thực hiện bởi một lớp trợ giúp.

Vui lòng xem this previous question.

Vui vẻ đủ điều này dẫn đến các sự cố khác, chẳng hạn như quyền anh và unboxing. Ứng dụng tôi đang làm việc đòi hỏi hiệu năng cực kỳ cao nên tôi cần tránh đấm bốc. Nếu bạn có thể nghĩ ra một cách tuyệt vời để xử lý chung các kiểu dữ liệu khác nhau (bao gồm DateTime) thì tôi là tất cả các tai!

0

Tại sao không chỉ triển khai giao diện mà bạn thực sự muốn và cho phép loại triển khai xác định giá trị là gì?Ví dụ:

class NumericValue : ITimedValue<float> { 
public TimeSpan TimeStamp { get; private set; } 
public float Value { get; private set; } 
} 

class DateTimeValue : ITimedValue<DateTime>, ITimedValue<float> { 
public TimeSpan TimeStamp { get; private set; } 
public DateTime Value { get; private set; } 
public Float ITimedValue<Float>.Value { get { return 0; } } 
} 

class NumericEvaluator { 
public void Evaluate(IEnumerable<ITimedValue<float>> values) ... 
} 

Nếu bạn muốn hành vi của việc thực hiện DateTime để thay đổi dựa trên việc sử dụng cụ thể (chẳng hạn, triển khai luân phiên của Đánh giá chức năng), sau đó họ theo định nghĩa cần phải nhận thức được ITimedValue<DateTime>. Bạn có thể nhận được một giải pháp tĩnh đánh máy tốt bằng cách cung cấp một hoặc nhiều hơn Converter đại biểu, ví dụ.

Cuối cùng, nếu bạn thực sự chỉ muốn xử lý các trường hợp NumericValue, chỉ lọc ra bất cứ điều gì đó không phải là một trường hợp NumericValue:

class NumericEvaluator { 
    public void Evaluate(IEnumerable<ITimedValue> values) { 
     foreach (NumericValue value in values.OfType<NumericValue>()) { 
      .... 
     } 
    } 
} 
+0

Tôi thực sự cân nhắc việc sử dụng 'OfType', nhưng tôi không thể nhớ tại sao tôi không trải qua nó. Đào qua các ghi chú, tôi đã tìm thấy một trường hợp dữ liệu của một loại khác được dự kiến ​​sẽ xuất hiện thỉnh thoảng dưới dạng một điểm đánh dấu. Tôi có thể liệt kê các bộ sưu tập nhiều lần để giải thích cho điều đó, nhưng tôi muốn tránh sự kém hiệu quả (đây là một hệ thống thời gian thực). – redman

+0

Làm thế nào về 'giá trị.Where (v => v là X | | v là Y)'? Mặc dù một lần nữa tôi hỏi những gì bạn có thể làm gì với một liệt kê không đồng nhất của X và Y, nếu họ không có một loại cơ sở chung hoặc giao diện. –

+0

Trong trường hợp này, họ đã cố gắng tiêm một số liệu thời gian vào dữ liệu bên ngoài dấu thời gian đã có sẵn. – redman

0

Tốt câu hỏi. Điều đầu tiên tôi nghĩ đến là một thuật toán Chiến lược phản chiếu. Thời gian chạy có thể cho bạn biết, hoặc tĩnh hoặc động, loại được dẫn xuất nhiều nhất của tham chiếu, bất kể loại biến bạn đang sử dụng để giữ tham chiếu. Tuy nhiên, thật không may, nó sẽ không tự động chọn một quá tải dựa trên kiểu dẫn xuất, chỉ có kiểu biến. Vì vậy, chúng ta cần phải hỏi trong thời gian chạy những gì các loại thực sự là, và dựa trên đó, tự chọn một quá tải cụ thể. Sử dụng sự phản chiếu, chúng ta có thể tự động xây dựng một tập hợp các phương thức được xác định là xử lý một loại phụ cụ thể, sau đó thẩm vấn tham chiếu cho kiểu chung của nó và tra cứu thực hiện trong từ điển dựa trên đó.

public interface ITimedValueEvaluator 
{ 
    void Evaluate(ITimedValue value); 
} 

public interface ITimedValueEvaluator<T>:ITimedValueEvaluator 
{ 
    void Evaluate(ITimedValue<T> value); 
} 

//each implementation is responsible for implementing both interfaces' methods, 
//much like implementing IEnumerable<> requires implementing IEnumerable 
class NumericEvaluator: ITimedValueEvaluator<int> ... 

class DateTimeEvaluator: ITimedValueEvaluator<DateTime> ... 

public class Evaluator 
{ 
    private Dictionary<Type, ITimedValueEvaluator> Implementations; 

    public Evaluator() 
    { 
     //find all implementations of ITimedValueEvaluator, instantiate one of each 
     //and store in a Dictionary 
     Implementations = (from t in Assembly.GetCurrentAssembly().GetTypes() 
     where t.IsAssignableFrom(typeof(ITimedValueEvaluator<>) 
     and !t.IsInterface 
     select new KeyValuePair<Type, ITimedValueEvaluator>(t.GetGenericArguments()[0], (ITimedValueEvaluator)Activator.CreateInstance(t))) 
     .ToDictionary(kvp=>kvp.Key, kvp=>kvp.Value);  
    } 

    public void Evaluate(ITimedValue value) 
    { 
     //find the ITimedValue's true type's GTA, and look up the implementation 
     var genType = value.GetType().GetGenericArguments()[0]; 

     //Since we're passing a reference to the base ITimedValue interface, 
     //we will call the Evaluate overload from the base ITimedValueEvaluator interface, 
     //and each implementation should cast value to the correct generic type. 
     Implementations[genType].Evaluate(value); 
    } 

    public void Evaluate(IEnumerable<ITimedValue> values) 
    { 
     foreach(var value in values) Evaluate(value); 
    } 
} 

Lưu ý rằng Người đánh giá chính là người đánh giá duy nhất có thể xử lý IE; mỗi lần triển khai ITimedValueEvaluator sẽ xử lý từng giá trị một. Nếu điều này là không khả thi (nói rằng bạn cần phải xem xét tất cả các giá trị của một loại cụ thể), sau đó điều này được thực sự dễ dàng; chỉ lặp qua mọi lần thực hiện trong Từ điển, chuyển nó qua IEnumerable đầy đủ, và có những triển khai đó lọc danh sách thành các đối tượng duy nhất của kiểu generic đóng cụ thể bằng phương thức OfType() Linq. Điều này sẽ yêu cầu bạn chạy tất cả các triển khai ITimedValueEvaluator mà bạn tìm thấy trong danh sách, đó là lãng phí công sức nếu không có mục nào của một kiểu cụ thể trong danh sách.

Vẻ đẹp của điều này là khả năng mở rộng của nó; để hỗ trợ một đóng mới chung của ITimedValue, chỉ cần thêm một triển khai mới của ITimedValueEvaluator cùng loại. Lớp Evaluator sẽ tìm thấy nó, khởi tạo một bản sao và sử dụng nó. Giống như hầu hết các thuật toán phản chiếu, nó chậm, nhưng phần phản chiếu thực tế là một giao dịch một lần.