许多构造函数参数 - 有更好的方法吗?

Lots of constructor parameters - Is there a better way?

public class HourlyForecastData
{
    public DateTime DateTime { get; private set; }
    public decimal TemperatureCelcius { get; private set; }
    public decimal DewPoint { get; private set; }
    public string Condition { get; private set; }
    public int ConditionCode { get; private set; }
    public int WindSpeed { get; private set; }
    public string WindDirection { get; private set; }
    public decimal WindDegrees { get; private set; }
    public int UltravioletIndex { get; private set; }
    public decimal Humidity { get; private set; }
    public decimal WindChill { get; private set; }
    public int HeatIndex { get; private set; }
    public decimal FeelsLike { get; private set; }
    public decimal Snow { get; private set; }

    public HourlyForecastData(DateTime dateTime, decimal temperatureCelcius, ...)
    {
        DateTime = dateTime;
        TemperatureCelcius = temperatureCelcius;
        //...set all the other properties via constructor
    }
}

我正在努力学习更好的软件设计和 OOP。我正在创建一个库,该库可以访问以 XML 回复的天气服务。该服务提供了许多不同的字段,因此我为每个 XML 字段创建了属性。但是,通过构造函数设置那么多属性感觉有点乱。我可以省略构造函数并使用 public 设置器,但我正在尝试制作一个不可变的 class.

我为此查看了不同的设计模式,似乎有一些 "Builder" 和 "Factory" 模式。但是,我很难理解如何将其应用到我的代码中。或者我应该使用完全不同的东西来填充这些对象中的属性?

Re 是否有更好的 OOP 方法?

class 上的大量属性通常表明需要拆分 class(SOLID 的 Single Responsibility Principle)。

例如看起来 HourlyForecastData 对风(速度和方向)、降水(雪、露和雨)和温度(最小值、最大值...)建模,这些问题可以拆分为单独的 classes,然后 HourlyForecastData 将是三者的组合。

回复:建造者模式

构建器模式可用于减轻构建大型(通常是不可变的)classes 或图形期间的负担,但显然需要额外的(可变的)Builder class(es) 来构建目标 class 表示(即 HourlyForecastData)并最终创建它(即,通过将所有参数传递给构造函数来不可变地构造它)。因此,如果那是 'better' 所要求的,那么这并不会减少工作量,但这当然可以更容易阅读,例如:

HourlyForecastData todaysForecast = new HourlyForecastDataBuilder()
   .WithBaseline(ObjectMother.WinterSnow) // Provide an archetype
   .WithPrecipitation(snow: 5, rain:1) // Dew defaults to 0
   .Build();

基线原型/object mothers 如果某个地区的天气模式经常稳定并且只需要进行小幅调整,则将很有用。 IMO 构建器模式在测试中最有用。我看不出明显适合 Xml 序列化用法。

另见 Named and Optional parameters

回复:不变性

私有 setter 在技术上仍然允许可变性,尽管在 class 本身内受到限制。 C#6 及更高版本支持 getter-only auto properties,这是实现不可变属性的最简单形式

public class HourlyForecastData
{
    public DateTime DateTime { get; }
    ...

    public HourlyForecastData(DateTime dateTime, ...)
    {
        // Get only auto properties can only be set at construction time
        DateTime = dateTime;
        ...

不相关,但 Scala offers an even more concise syntax than C# 用于在 class 上定义不可变的 public 属性,方法是在(主)构造函数中声明一次:

class HourlyForecastData(val temperature: Int, val station: String, ...) {
}

不需要任何进一步的 属性 或支持字段,同时表达和执行不变性。但是,调用者仍然需要承担提供所有参数的负担(无论是直接提供,还是通过 Builder 等提供)。

回复:Xml 如果您提供 API,我建议您使用 WebAPI。我建议不要将 Xml 序列化问题构建到您的 DTO classes 中,而是依赖 Content Negotiation。这将允许调用者确定是否应以 Xml 或 JSON 格式返回数据。

* 但是请注意,Xml 反序列化技术通常使用反射来填充 DTO 属性,这可能要求可序列化属性具有 setter(即使是私有的)。

一种方法是使用结构并将其传入。它还使使用 class 更容易,因为您只需要声明结构状态变量,更改与 "default" 不同的内容,然后将其传入。

public struct HourlyForecastDataState
{
    public DateTime DateTime;
    public decimal TemperatureCelcius;
    public decimal DewPoint;
    public string Condition;
    public int ConditionCode;
    public int WindSpeed;
    public string WindDirection;
    public decimal WindDegrees;
    public int UltravioletIndex;
    public decimal Humidity;
    public decimal WindChill;
    public int HeatIndex;
    public decimal FeelsLike;
    public decimal Snow;
}

public class HourlyForecastData
{
    public DateTime DateTime { get; private set; }
    public decimal TemperatureCelcius { get; private set; }
    public decimal DewPoint { get; private set; }
    public string Condition { get; private set; }
    public int ConditionCode { get; private set; }
    public int WindSpeed { get; private set; }
    public string WindDirection { get; private set; }
    public decimal WindDegrees { get; private set; }
    public int UltravioletIndex { get; private set; }
    public decimal Humidity { get; private set; }
    public decimal WindChill { get; private set; }
    public int HeatIndex { get; private set; }
    public decimal FeelsLike { get; private set; }
    public decimal Snow { get; private set; }

    public HourlyForecastData(HourlyForecastDataState state)
    {
        DateTime = state.dateTime;
        TemperatureCelcius = state.temperatureCelcius;
        //...etc
    }
}

//Usage:
HourlyForecastDataState HFDstate = new HourlyForecastDataState();
HFDstate.temperatureCelcius = 100 //omg, it's hot!

HourlyForecastData HFD = new HourlyForecastData(HFDstate);

在这种情况下,组合可能很合适。特别是因为有一些参数属于特定类别。

例如:

public int WindSpeed;
public string WindDirection;
public decimal WindDegrees;

为它们创建一个新对象,然后访问不同的值:

weatherData.Wind.Speed;

并将新的风对象传递给构造函数:

var wind = new Wind(xmlData.WindSpeed, xmlData.WindDirection, xmldata.WindDegrees);
var weatherReport = new WeatherReport(wind, /* .... */);

我还要介绍几个枚举。因为截至目前,weatherReport 的用户必须知道字符串 WindDirection 可以具有哪些值。如果您将字符串转换为枚举,则使用不同的值会容易得多。

最后要注意的是,我通常仅在确实必须为 class 指定某些值才能具有有效状态时才使用构造函数。例如,在您的情况下,最低有效状态是日期和温度?然后把它们放在构造函数中。