Bạn có một khởi đầu tốt, nhưng như Jon đã nói, hiện tại nó không an toàn; bộ chuyển đổi không có kiểm tra lỗi để đảm bảo số thập phân mà nó nhận được là giá trị Celsius.
Vì vậy, để thực hiện điều này hơn nữa, tôi sẽ bắt đầu giới thiệu các loại cấu trúc có giá trị bằng số và áp dụng nó cho một đơn vị đo lường. Trong các mẫu Kiến trúc doanh nghiệp (còn gọi là mẫu thiết kế Gang of Four), mẫu này được gọi là mẫu "Tiền" sau khi sử dụng phổ biến nhất, để biểu thị số lượng loại tiền tệ. Mẫu giữ cho bất kỳ số lượng nào yêu cầu đơn vị đo lường có ý nghĩa.
Ví dụ:
public enum TemperatureScale
{
Celsius,
Fahrenheit,
Kelvin
}
public struct Temperature
{
decimal Degrees {get; private set;}
TemperatureScale Scale {get; private set;}
public Temperature(decimal degrees, TemperatureScale scale)
{
Degrees = degrees;
Scale = scale;
}
public Temperature(Temperature toCopy)
{
Degrees = toCopy.Degrees;
Scale = toCopy.Scale;
}
}
Bây giờ, bạn có một loại đơn giản mà bạn có thể sử dụng để thực thi là chuyển đổi bạn đang làm mất một nhiệt độ đó là quy mô thích hợp, và trả về một Nhiệt độ kết quả được biết đến là ở quy mô khác.
Funcs của bạn sẽ cần thêm dòng để kiểm tra xem đầu vào có khớp với đầu ra hay không; bạn có thể tiếp tục sử dụng lambdas, hoặc bạn có thể mang nó thêm một bước nữa với một mô hình chiến lược đơn giản:
public interface ITemperatureConverter
{
public Temperature Convert(Temperature input);
}
public class FahrenheitToCelsius:ITemperatureConverter
{
public Temperature Convert(Temperature input)
{
if (input.Scale != TemperatureScale.Fahrenheit)
throw new ArgumentException("Input scale is not Fahrenheit");
return new Temperature(input.Degrees * 5m/9m - 32, TemperatureScale.Celsius);
}
}
//Implement other conversion methods as ITemperatureConverters
public class TemperatureConverter
{
public Dictionary<Tuple<TemperatureScale, TemperatureScale>, ITemperatureConverter> converters =
new Dictionary<Tuple<TemperatureScale, TemperatureScale>, ITemperatureConverter>
{
{Tuple.Create<TemperatureScale.Fahrenheit, TemperatureScale.Celcius>,
new FahrenheitToCelsius()},
{Tuple.Create<TemperatureScale.Celsius, TemperatureScale.Fahrenheit>,
new CelsiusToFahrenheit()},
...
}
public Temperature Convert(Temperature input, TemperatureScale toScale)
{
if(!converters.ContainsKey(Tuple.Create(input.Scale, toScale))
throw new InvalidOperationException("No converter available for this conversion");
return converters[Tuple.Create(input.Scale, toScale)].Convert(input);
}
}
Bởi vì các loại chuyển đổi hai chiều, bạn có thể xem xét thiết lập giao diện để xử lý cả hai cách, với phương thức "ConvertBack" hoặc tương tự sẽ lấy Nhiệt độ theo thang đo Celsius và chuyển sang Fahrenheit. Điều đó làm giảm số lượng lớp học của bạn. Sau đó, thay vì các cá thể lớp, các giá trị từ điển của bạn có thể là các con trỏ tới các phương thức trên các cá thể của các trình biến đổi. Điều này làm tăng độ phức tạp của việc thiết lập bộ chọn chiến lược TemperatureConverter chính, nhưng làm giảm số lượng các lớp chiến lược chuyển đổi mà bạn phải xác định.Cũng cần lưu ý rằng việc kiểm tra lỗi được thực hiện khi chạy trên thực tế khi bạn thực sự cố gắng thực hiện chuyển đổi, yêu cầu mã này phải được kiểm tra kỹ trong tất cả các tập quán để đảm bảo mã luôn đúng. Để tránh điều này, bạn có thể lấy được lớp Nhiệt độ cơ bản để tạo ra các cấu trúc CelsiusTemperature và FahrenheitTemperature, mà chỉ đơn giản là xác định Thang đo của chúng như là một giá trị không đổi. Sau đó, ITemperatureConverter có thể được tạo thành chung cho hai loại, cả hai Nhiệt độ, cho phép bạn kiểm tra thời gian biên dịch mà bạn đang xác định chuyển đổi mà bạn nghĩ bạn đang có. các TemperatureConverter cũng có thể được thực hiện để tự động tìm ITemperatureConverters, xác định các loại họ sẽ chuyển đổi giữa, và automagically thiết lập từ điển chuyển đổi, do đó bạn không bao giờ phải lo lắng về việc thêm những cái mới. Điều này xảy ra với chi phí tăng số lượng lớp học dựa trên Nhiệt độ; bạn sẽ cần bốn lớp miền (một cơ sở và ba lớp dẫn xuất) thay vì một. Nó cũng sẽ làm chậm việc tạo ra một lớp TemperatureConverter như mã để xây dựng một cách phản ánh từ điển chuyển đổi sẽ sử dụng khá nhiều sự phản ánh.
Bạn cũng có thể thay đổi enums cho các đơn vị đo lường để trở thành "lớp đánh dấu"; các lớp trống không có ý nghĩa nào khác hơn là chúng thuộc lớp đó và xuất phát từ các lớp khác. Sau đó bạn có thể định nghĩa một hệ thống phân cấp đầy đủ của các lớp "UnitOfMeasure" đại diện cho các đơn vị đo lường khác nhau và có thể được sử dụng như các đối số và ràng buộc kiểu chung chung; ITemperatureConverter có thể là chung cho hai loại, cả hai loại này đều bị ràng buộc là các lớp TemperatureScale và việc triển khai CelsiusFahrenheitConverter sẽ đóng giao diện chung cho các loại CelsiusDegrees và FahrenheitDegrees đều có nguồn gốc từ TemperatureScale. Điều đó cho phép bạn vạch trần các đơn vị đo lường như là các ràng buộc của một chuyển đổi, lần lượt cho phép chuyển đổi giữa các loại đơn vị đo lường (một số đơn vị vật liệu nhất định đã biết chuyển đổi; 1 Pint Imperial nước nặng 1,25 pound).
Tất cả những quyết định thiết kế này sẽ đơn giản hóa một loại thay đổi đối với thiết kế này, nhưng với một số chi phí (hoặc làm điều gì đó khó khăn hơn để thực hiện hoặc giảm hiệu suất thuật toán). Việc quyết định điều gì thực sự "dễ dàng" đối với bạn, trong bối cảnh của ứng dụng tổng thể và môi trường mã hóa bạn làm việc.
CHỈNH SỬA: Cách sử dụng bạn muốn, rất dễ dàng với nhiệt độ . Tuy nhiên, nếu bạn muốn một UnitConverter chung có thể làm việc với bất kỳ UnitofMeasure nào, thì bạn không còn muốn Enums đại diện cho các đơn vị đo lường của bạn nữa, bởi vì Enums không thể có một hệ thống phân cấp thừa kế tùy chỉnh (chúng lấy trực tiếp từ System.Enum).
Bạn có thể chỉ định hàm tạo mặc định có thể chấp nhận bất kỳ Enum nào, nhưng bạn phải đảm bảo rằng Enum là một trong các loại đơn vị đo lường, nếu không bạn có thể chuyển giá trị DialogResult và trình biến đổi trong thời gian chạy. Thay vào đó, nếu bạn muốn một UnitConverter có thể chuyển đổi sang bất kỳ UnitOfMeasure nào cho lambdas cho các đơn vị đo lường khác, tôi sẽ chỉ định đơn vị đo lường là "các lớp đánh dấu"; nhỏ stateless "thẻ" mà chỉ có ý nghĩa ở chỗ chúng là kiểu riêng của họ và lấy được từ các bậc phụ huynh:
//The only functionality any UnitOfMeasure needs is to be semantically equatable
//with any other reference to the same type.
public abstract class UnitOfMeasure:IEquatable<UnitOfMeasure>
{
public override bool Equals(UnitOfMeasure other)
{
return this.ReferenceEquals(other)
|| this.GetType().Name == other.GetType().Name;
}
public override bool Equals(Object other)
{
return other is UnitOfMeasure && this.Equals(other as UnitOfMeasure);
}
public override operator ==(Object other) {return this.Equals(other);}
public override operator !=(Object other) {return this.Equals(other) == false;}
}
public abstract class Temperature:UnitOfMeasure {
public static CelsiusTemperature Celsius {get{return new CelsiusTemperature();}}
public static FahrenheitTemperature Fahrenheit {get{return new CelsiusTemperature();}}
public static KelvinTemperature Kelvin {get{return new CelsiusTemperature();}}
}
public class CelsiusTemperature:Temperature{}
public class FahrenheitTemperature :Temperature{}
public class KelvinTemperature :Temperature{}
...
public class UnitConverter
{
public UnitOfMeasure BaseUnit {get; private set;}
public UnitConverter(UnitOfMeasure baseUnit) {BaseUnit = baseUnit;}
private readonly Dictionary<UnitOfMeasure, Func<decimal, decimal>> converters
= new Dictionary<UnitOfMeasure, Func<decimal, decimal>>();
public void AddConverter(UnitOfMeasure measure, Func<decimal, decimal> conversion)
{ converters.Add(measure, conversion); }
public void Convert(UnitOfMeasure measure, decimal input)
{ return converters[measure](input); }
}
Bạn có thể đưa vào kiểm tra lỗi (kiểm tra xem các đơn vị đầu vào có một sự chuyển đổi quy định, kiểm tra xem một chuyển đổi đang được thêm vào là cho một UOM có cùng một kiểu gốc như kiểu cơ sở, vv ..) khi bạn thấy phù hợp. Bạn cũng có thể lấy được UnitConverter để tạo ra TemperatureConverter, cho phép bạn thêm các kiểm tra kiểu tĩnh, biên dịch-thời gian và tránh các kiểm tra thời gian chạy mà UnitConverter sẽ phải sử dụng.
Đơn vị đo được hỗ trợ trong F # và tôi nghĩ rằng nó có thể là một [Tính năng] tốt (https://github.com/dotnet/roslyn/issues/144) cho C# vNext. Bây giờ tôi đã tìm thấy dự án này [QuantityTypes] (https://github.com/objorke/QuantityTypes) để thực hiện các đơn vị đo lường trong C#. – orad