【问题标题】:How can I combine two classes with common members into a single input?如何将两个具有共同成员的类组合成一个输入?
【发布时间】:2018-05-18 13:53:10
【问题描述】:

我需要创建一个接受 3 个输入的服务,基本上可以归结为 AB,以及 AB 的组合,我们将其称为 C

假设这些类定义如下:

public abstract class InputBase
{
  public bool Option1 { get; set; }
  public decimal Rate { get; set; }
  public DateTime DateCreated { get; set; }
}

public class A : InputBase
{
  public decimal Fee { get; set; }
}

public class B : InputBase
{
  public decimal Fee { get; set; }
}

AB 中的 Fees 是不同的和独立的,并且可以是不同的,对于像 C 这样的情况,您可以在同一个请求中组合使用 AB .

我们目前只有一个如下所示的类:

public class C
{
  public A A { get; set; }
  public B B { get; set; }
}

但是,这使得更新两者中的任何共同值变得非常烦人。例如:

var c = new C
{
  A = new A(),
  B = new B()
}

//this is ugly and what we want to avoid
c.A.DateCreated = DateTime.Now;
c.B.DateCreated = DateTime.Now;

我们希望能够做这样的事情:

c.DateCreated = DateTime.Now;
//later
var createdDate = c.A.DateCreated; //points to date created before

这样做的天真方法是实现 setter 并使用值更新两个内部类,但是我们如何处理 getter 呢?

另外,C 是否应该继承自 InputBase,因为它在技术上是服务的输入?

解决这个问题的正确方法是什么?

【问题讨论】:

  • 您要求保留单独的实例,但希望它们组合成一个实例。 Fee 是唯一具有不同的字段还是对象中有其他东西使它们不同?
  • 还有其他的,我只是用了一个字段来保持简洁。
  • 我在您的设计中感觉到强烈的代码气味。如果您想同时发送AB,您应该发送IEnumerable<InputBase>。如果某些属性需要具有相同的值,那么如何将它们一起操作是一个不同的问题,并且可以通过公开方法的辅助类来解决,例如 AddInputBaseSetDateCreated
  • AB 都是可设置的,它们的 DateCreated 属性也是可设置的,这意味着您不能保持它们的 DateCreated 属性同步。您可以按照答案中的建议同时设置它们,但是任何一个都可以独立更改或替换为另一个实例。当您确实无法确保它们同步时,尝试确保它们同步似乎很奇怪。
  • @GhostCat 谢谢。这个问题看起来就像一个没有付出任何努力的家庭作业问题。你说得对,我不应该把它投票为需要编辑。我不知道处理这种情况的正确方法是什么。

标签: c# .net inheritance composition


【解决方案1】:

您的继承结构存在某些问题。我不反对其他答案,但我觉得他们已经被这些无关紧要的元素分心了。

我试图只回答你自己已经回答的直接问题:

这样做的天真方法是实现 setter 并使用值更新两个内部类,但是我们如何处理 getter 呢?

您对 getter 问题是正确的,但请记住,getter 不需要存在。您可以拥有只写属性。它们很少见,但它们是有目的的。

我也不确定究竟是什么天真。简单的解决方案还不错。如果有的话,简单的解决方案比复杂的解决方案要好,如果他们设法解决同样的问题。 KISS 适用:

KISS 原则指出,大多数系统如果保持简单而不是复杂化,则效果最好;因此,简单性应该是设计的一个关键目标,并且应该避免不必要的复杂性。

注意对不必要的复杂性的强调。如果有必要,那么它(根据定义)是有目的的。


var c = new C
{
  A = new A(),
  B = new B()
}

//this is ugly and what we want to avoid
c.A.DateCreated = DateTime.Now;
c.B.DateCreated = DateTime.Now;

一个简单的解决方法是在C 中创建一个自定义属性:

public class C
{
    public DateTime DateCreated 
    {
        set
        {
            this.A.DateCreated = value;
            this.B.DateCreated = value;
        }
    }
}

我省略了 getter,因为它没有多大意义(你会显示 A.CreatedOnB.CreatedOn)吗?
但简单地省略 getter 并没有错,所以这似乎是这里最好的方法。

这会创建您想要的行为:

//sets both values
c.DateCreated = DateTime.Now; 

但是,值是单独检索的:

var createdDateA = c.A.DateCreated;
var createdDateB = c.B.DateCreated;

这样做的天真方法是实现 setter 并使用值更新两个内部类,但是我们如何处理 getter 呢?

除了我之前对此的反馈之外,如果您真的不喜欢 writeonly 属性,您基本上可以将它们转换为方法。功能等效,但更明确地不公开 get 方法:

public class C
{
    public void SetDateCreated(DateTime value)
    {
        this.A.DateCreated = value;
        this.B.DateCreated = value;
    }
}

此外,C 是否应该从 InputBase 继承,因为它在技术上是服务的输入?

除非 C 有自己的 Fee 对象,该对象与其他派生的 InputBase 类完全相同。

这个问题有点笼统;它取决于代码库中的继承(和 SOLID)问题。这对于 StackOverflow 问题来说太大了。如果您可以将其改写为关于继承结构的具体问题,它可能更适合 SoftwareEngineering.SE(或者可能是 CodeReview.SE,如果您有工作代码)。

【讨论】:

    【解决方案2】:

    您可以让 C 派生自 InputBase,以及拥有 InputBase 的集合。

    public class C : InputBase, IHaveInputBase
    {
        IList<InputBase> IHaveInputBase.Inputs { get; set; }
    }
    
    public class Service
    {
        public void Handle(InputBase inputBase)
        {
            IHaveInputBase haveInputBase = inputBase as IHaveInputBase;
    
            if (haveInputBase != null)
            {
                foreach (InputBase input in haveInputBase.Inputs) 
                {
                    this.Handle(input);
                }
            }
    
            inputBase.DateCreated = this.dateTimeService.UtcNow();
            ...
        }
    }
    

    【讨论】:

    • 这并不能解决不能做C.DateCreated = DateTime.Now的问题。
    • 因为他说他需要额外的逻辑来操纵“孩子”。
    • 这篇文章的意思不是要阅读:public class C : IList { }(或类似的东西吗?
    • @Logan - 仅当 C 需要 DateCreated 时。如果是这样,并且它确实需要是 InputBase,那么您可以从中派生,并以不同的方式处理它。我更新了我的答案。
    • @JuanR - 我相信我最初的回答是这样做的。无论如何,我已经对其进行了更新以使其更加清晰。
    【解决方案3】:

    我可能没有正确理解你,但你为什么不让C 继承自InputBase 并拥有一个AFeeBFee

    class C: InputBase {
        public decimal AFee { get; set; }
        public decimal BFee { get; set; }
    }
    

    然后您可以为C 添加构造函数,该构造函数接受AB。这将检查它们是否具有相同的Option1RateDateCreated。如果有,请为 C 字段分配它们的值。如果他们不这样做,请抛出异常或类似的东西。

    您还可以使用GetAGetB 之类的方法从C 的值创建AB 对象。

    class C : InputBase {
        public decimal AFee { get; set; }
        public decimal BFee { get; set; }
    
        public C(A a, B b) {
            if (a.DateCreated == b.DateCreated && a.Option1 == b.Option1 && a.Rate == b.Rate) {
                Option1 = a.Option1;
                Rate = a.Rate;
                DateCreated = a.DateCreated;
            } else {
                throw new ArgumentException("...");
            }
        }
    
        public C() {}
    
        public A GetA() {
            return new A { DateCreated = DateCreated, Option1 = Option1, Rate = Rate, Fee = AFee };
        }
    
        public B GetB() {
            return new B { DateCreated = DateCreated, Option1 = Option1, Rate = Rate, Fee = BFee };
        }
    }
    

    【讨论】:

      【解决方案4】:

      1.您可以使用更新输入的方法来采取行动

      public class C 
      {
          public A A { get; }
          public B B { get; }
      
          public void UpdateInput(Action<InputBase> updateFunc)
          {
              updateFunc(A);
              updateFunc(B);
          }
      }
      

      然后只需通过 Action 更新您的任何 InputBase 属性。

      var c = new C
      {
        A = new A(),
        B = new B()
      }
      var dateTime = DateTime.UtcNow;
      c.UpdateInput(input => input.DateCreated = dateTime);
      

      2. 或者您可以将 InputBase 的属性设为虚拟并像这样覆盖它们

      public abstract class InputBase
      {
          public virtual bool Option1 { get; set; }
          public virtual decimal Rate { get; set; }
          public virtual DateTime DateCreated { get; set; }
      }
      
      public class C : InputBase
      {
          private bool _option1;
          public override bool Option1
          {
              get => _option1;
              set
              {
                  _option1 = value;
                  A.Option1 = value;
                  B.Option1 = value;
              }
          }
      
          private decimal _rate;
          public override decimal Rate
          {
              get => _rate;
              set
              {
                  _rate = value;
                  A.Rate = value;
                  B.Rate = value;
              }
          }
      
          private DateTime _dateCreated;
          public override DateTime DateCreated
          {
              get => _dateCreated;
              set
              {
                  _dateCreated = value;
                  A.DateCreated = value;
                  B.DateCreated = value;
              }
          }
      
          public A A { get; }
          public B B { get; }
      }
      

      【讨论】:

        猜你喜欢
        • 2016-04-27
        • 2016-05-25
        • 2014-01-12
        • 2016-07-20
        • 1970-01-01
        • 2022-01-24
        • 1970-01-01
        • 1970-01-01
        • 2012-10-22
        相关资源
        最近更新 更多