【问题标题】:Lots of constructor parameters - Is there a better way?很多构造函数参数 - 有没有更好的方法?
【发布时间】:2015-03-22 06:19:00
【问题描述】:
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 字段创建了属性。但是,通过构造函数设置这么多属性感觉有点混乱。我可以省略构造函数并拥有公共设置器,但我正在尝试创建一个不可变类。

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

【问题讨论】:

  • 这些属性都需要吗?
  • 所有属性都有默认值吗?
  • 所有属性都是必需的。我认为没有任何类型的默认值,Web 服务文档对此没有提及。
  • 不变性真的那么重要吗?看起来像一个简单的 DTO,所以我将属性公开,然后让 XMLSerializer 自动填充它。
  • 试试“builder”模式。但它不会从根本上解决你的问题

标签: c# oop design-patterns


【解决方案1】:

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

例如:

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 可以具有哪些值。如果将字符串转换为枚举,则使用不同的值会容易得多。

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

【讨论】:

  • 组合是个好主意。它会在反序列化过程中引入更多复杂性,但您最终会得到更干净的业务实体。
  • 这是一个非常有帮助的答案,谢谢。您说只是在构造函数中设置了所需的最小值,但是如果不通过构造函数,通过公共设置器,我将如何设置其他属性?
  • @user9993:如果你不想使用公共设置器,你可以使用反射obj.GetType().GetProperty("PropertyName").SetValue(obj, value); 我认为在将信息从 DTO 转换为实体对象的类中是可以接受的。
【解决方案2】:

是否有更好的 OOP 方法?

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

例如似乎HourlyForecastData 模型风(速度和方向)、降水(雪、露水和雨)和温度(最小、最大 ...)这些问题可以分成不同的类别,然后 HourlyForecastData 将三者合一。

Re : Builder 模式

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

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

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

另见Named and Optional parameters

回复:不变性

从技术上讲,私有 setter 仍然允许可变性,尽管仅限于类本身。 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 HourlyForecastData(val temperature: Int, val station: String, ...) {
}

无需任何其他属性或支持字段,同时表达和执行不变性。然而,提供所有参数的负担仍然落在调用者身上(无论是直接提供,还是通过 Builder 等)。

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

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 2011-12-19
      • 2019-07-13
      • 2014-06-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-16
      • 2016-11-12
      相关资源
      最近更新 更多