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

时间:2015-03-22 06:19:01

标签: c# oop design-patterns

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字段创建了属性。但是,通过构造函数设置该数量的属性会感觉有点混乱。我可以省略构造函数并拥有公共setter但我正在尝试创建一个不可变类。

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

3 个答案:

答案 0 :(得分:8)

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

例如:

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

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

weatherData.Wind.Speed;

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

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

我还会介绍一些枚举。因为到目前为止,weatherReport的用户必须知道字符串WindDirection可以具有哪些值。如果将字符串转换为枚举,则使用不同的值会更容易。

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

答案 1 :(得分:3)

Re是否有更好的OOP方法?

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

e.g。似乎HourlyForecastData模型风(速度和方向),降水(雪,露和雨)和温度(最小,最大......)这些问题可以分成单独的类,然后是{{ 1}}将是三者的组合。

Re:Builder Pattern

构建器模式可以在构建大型(通常是不可变的)类或图形时减轻负担,但显然需要额外的(可变的)Builder类来构建目标类表示(即{{1并最终创建它(即,通过将所有参数传递给构造函数来不可变地构造它)。因此,如果这是“更好”所要求的,那就不是那么努力了,但这当然可以更容易阅读,例如:

HourlyForecastData

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

另见Named and Optional parameters

不相关,但Scala offers a more pleasant syntax than C# IMO用于在类上定义不可变的公共属性,方法是在(主)构造函数中声明它们一次:

HourlyForecastData

在表达和强制执行不变性的同时,无需任何其他财产或支持领域。但是,调用者仍然需要提供所有参数(无论是直接,还是通过Builder等)。

Re:不变性

如果要保留私有setter封装和HourlyForecastData todaysForecast = new HourlyForecastDataBuilder() .WithBaseline(ObjectMother.WinterSnow) // Provide an archetype .WithPrecipitation(snow: 5, rain:1) // Dew defaults to 0 .Build(); 属性的伪“外部”不变性,则需要继续通过构造函数显式传递和设置所有属性。

对于真正的“纯粹”不变性,您还应该理想地用class HourlyForecastData(val temperature: Int, val station: String, ...) { } 支持字段替换自动属性,并将private-set放在属性上,这将限制属性,以便它们只能是在构造函数中设置(即,否则private readonly仍允许类在构造后内部改变属性)。

但是,如果不可变性(即使只是外部) Not required *,您可以删除私有设置者并设置属性设置器private set

private set

然后,如果您有一个默认构造函数,那么您可以使用初始化语法在类似的构造语法中提供尽可能少的属性(其余属性将采用默认值):

public

public class HourlyForecastData { public DateTime DateTime { get; set; } public decimal TemperatureCelcius { get; set; } .... 注意Xml反序列化技术通常使用反射,这将要求可序列化属性具有公共设置器。

答案 2 :(得分:-2)

一种方法是使用结构并将其传递给它。它还使得使用类变得更容易,因为您只需要声明结构状态变量,更改与“默认”不同的任何内容然后将其传入。

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