什么是在同一类的其他属性依赖下强制正确值的可接受方式?

3

假设我有一个类,它有以下三个属性。

public class Travel
{
  public int MinAirportArrival { get; set; }
  public int MinFlightTime { get; set; }
  public int TotalTravelTime { get; set; }
}

TotalTravelTime必须至少等于MinAirportArrival和MinFlightTime之和,但在有中转或类似情况下也可能更长。

我认为可以在TotalTravelTime的setter中加入逻辑。

我的问题是关于更改MinFlightTime和MinAirportArrival。是否正确期望先增加TotalTravelTime,如果没有,则在其他属性使得总和大于TotalTravelTime时抛出异常?

是否还有其他合理的控制方式?

我应该让负责保存状态的对象检查类上的有效属性吗?我可能还有其他逻辑要放进去。

编辑
如果存在额外时间,我并没有在任何地方存储它,因此这不仅仅是添加一些属性的问题。请注意,这个类只是一个编造的例子,用来说明我所面临的问题,但我认为它很好地匹配了这个问题。

5个回答

7

框架设计准则建议属性不应相互依赖。 应始终可以以任意顺序设置多个属性的值

以下是我看到的选项:

  1. In this specific case:

    public class Travel
    {
      public int MinAirportArrival { get; set; }
      public int MinFlightTime { get; set; }
      public int AdditionalTravelTime { get; set; }
      public int TotalTravelTime
      {
        get { return MinAirportArrival + MinFlightTime + AdditionalTravelTime; }
      }
    }
    

    This takes advantage of the effect that the TotalTravelTime can be reconstructed from the three other values which can be indivually set without dependencies.

  2. A solution for the general case is to accept any value and validate the values when, for example, the instance is sent to storage.

    public class Travel
    {
      public int MinAirportArrival { get; set; }
      public int MinFlightTime { get; set; }
      public int TravelTime { get; set; }
      public void Save()
      {
        // validate TravelTime > MinAirportArrival + MinFlightTime 
      }
    }
    
  3. Another option is to make the properties read-only and provide a method to batch update the values of the properties.

    public class Travel
    {
      public int MinAirportArrival { get; private set; }
      public int MinFlightTime { get; private set; }
      public int TravelTime { get; private set; }
      public void UpdateTimes(
        int minAirportArrival, int minFlightTime, int travelTime)
      {
        // validate travelTime > minAirportArrival + minFlightTime 
        MinAirportArrival = minAirportArrival;
        MinFlightTime = minFlightTime;
        TravelTime = travelTime;
      }
    }
    
  4. Alternatively, you can make Travel objects immutable and use a constructor, factory method or mutable builder object to create instances.

    Constructor:

    public class Travel
    {
      public Travel(int minAirportArrival, int minFlightTime, int travelTime)
      {
        // validate travelTime > minAirportArrival + minFlightTime 
      }
      public int MinAirportArrival { get; }
      public int MinFlightTime { get; }
      public int TravelTime { get; }
    }
    

    Factory method:

    public class Travel
    {
      public static Travel CreateTravel(
        int minAirportArrival, int minFlightTime, int travelTime)
      {
        // validate travelTime > minAirportArrival + minFlightTime 
        return new Travel(minAirportArrival, minFlightTime, travelTime);
      }
      private Travel(int minAirportArrival, int minFlightTime, int travelTime);
      public int MinAirportArrival { get; }
      public int MinFlightTime { get; }
      public int TravelTime { get; }
    }
    

    Builder class:

    public class TravelBuilder
    {
      public int MinAirportArrival { get; set; }
      public int MinFlightTime { get; set; }
      public int TravelTime { get; set; }
      public Travel BuildTravel()
      {
        // validate TravelTime > MinAirportArrival + MinFlightTime 
        return new Travel(MinAirportArrival, MinFlightTime, TravelTime);
      }
    }
    

    Examples of all three options can be found in the .NET framework.


AdditionalTravelTime 可能是各种不同的事物,目前没有被存储。 - YonahW
但是TotalTravelTime存储在您的代码中。我的建议是,不要存储三元组(MinAirportArrival, MinFlightTime, TotalTravelTime),而是存储(MinAirportArrival, MinFlightTime, AdditionalTravelTime),它表示完全相同的数据,只是与已知的TotalTravelTime不同,而不是TotalTravelTime本身。 - dtb

1
为了允许以任意顺序设置属性,但保留已建立的验证 API(某些 API 自动使用,包括 DataGridView 和 ASP.NET MVC 验证),一种选择是使用 IDataErrorInfo。这不是最简单的接口实现,但是可行的(如下所示)。您可以将验证概括为以下内容:
IDataErrorInfo dei = obj as IDataErrorInfo;
if(dei != null) {
    string err = dei.Error;
    if(!string.IsNullOrEmpty(err)) throw new SomeTypeOfException(err);
}

这里是一个实现:

public class Travel : IDataErrorInfo
{
    public int MinAirportArrival { get; set; }
    public int MinFlightTime { get; set; }
    public int TotalTravelTime { get; set; }
    string IDataErrorInfo.this[string property] {
        get {
            switch (property) {
                case "TotalTravelTime":
                    if (TotalTravelTime < MinAirportArrival + MinFlightTime) {
                        return "not enough time blah";
                    }
                    break;
            }
            return "";
        }
    }
    string IDataErrorInfo.Error {
        get {
            StringBuilder sb = new StringBuilder();
            AppendError(ref sb, "MinAirportArrival");
            AppendError(ref sb, "MinFlightTime");
            AppendError(ref sb, "TotalTravelTime");
            return sb == null ? "" : sb.ToString();
        }
    }
    void AppendError(ref StringBuilder builder, string propertyName) {
        string error = ((IDataErrorInfo)this)[propertyName];
        if (!string.IsNullOrEmpty(error)) {
            if (builder == null) {
                builder = new StringBuilder(error);
            } else {
                builder.Append(error);
            }
        }
    }
}

1

由于TotalTravelTime是从另外两个属性计算得出的,因此我不会为其实现公共setter方法,而是使用一个私有的setter方法,该方法由另外两个属性的setter方法更新。


它并不是完全派生的。它只需要是总和的最小值。 - YonahW

0
我认为最好将自定义验证逻辑放入一个私有的辅助方法中,然后从每个属性设置器中调用此方法,就像这样:
public class Travel
{
    private int minAirportArrival;
    private int minFlightTime;
    private int additionalTravelTime;

    public int MinAirportArrival
    { 
        get
        {
            return this.minAirportArrival;
        }
        set
        {
            ThrowOnImpossibleTimes(value, this.minFlightTime, this.additionalTravelTime);
            this.minAirportArrival = value;
        }
    }

    // other properties...

    private static void ThrowOnImpossibleTimes(int minAirportArrival, int minFlightTime, int additionalTravelTime)
    {
        // do check and eventually throw...
    }
}

0
一种实现的方法是通过实现一个验证方法来检查条件。该方法将从每个属性的setter中调用,并在条件不满足时抛出异常。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接