【问题标题】:Object Orientated Design Parent / Child Relationship面向对象设计父/子关系
【发布时间】:2011-04-21 23:03:35
【问题描述】:

这是关于创建与对象的父/子关系的一般最佳实践问题。

假设我有 Wheel 和 Car 对象,我想将 Wheel 对象添加到 car 对象

public class Car{

    private List<Wheel> wheels = new List<Wheel>();

    void AddWheel ( Wheel WheelToAdd)
        {
            wheels.Add(WheelToAdd)
            //Some Other logic relating to adding wheels here
        }
    }
}

到目前为止一切顺利。但是,如果我想拥有我的车轮的 Car 属性来说明它与哪辆母车有关。像这样的

 public class Wheel {

     private Car parentCar;
     public Car 
     {
        get
        {
         return parentCar
        }

  }

}

当给汽车添加轮子时,你会在什么时候设置轮子的父属性?您可以在 Car.AddWheel 方法中设置它,但是 Wheel 对象的 Car 属性必须是读/写,然后您可以在 AddWheel 方法之外设置它,从而产生不一致。

任何想法,非常感谢提前

【问题讨论】:

    标签: c# oop


    【解决方案1】:

    一种更好的设计方法,(域驱动设计)指定您应该首先确定这些实体的域模型要求是什么......并非所有实体都需要独立访问,如果Wheel属于这一类,它的每个实例都将始终是 Car 对象的子成员,您不需要在其上放置 Parent 属性...Car 成为所谓的根实体,而访问Wheel 的唯一方法是通过Car 对象。

    即使Wheel 对象需要可独立访问,域模型要求也应该告诉您使用模式需要什么。任何Wheel 是否会作为一个单独的对象传递,没有它的父对象?在这些情况下,Car 父母是否相关?如果父 Car 的身份与某些功能相关,为什么不简单地将完整的复合 Car 对象传递给该方法或模块?包含的复合对象(如Wheel)必须自己传递,但需要和/或相关父对象(它所属的对象)的身份的情况实际上并不常见,并且使用上述类型的分析来接近您的设计可以使您免于向系统添加不必要的代码。

    【讨论】:

    • 在我的特定场景中,我有乘客和队列,因此乘客可以独立存在。也许我应该使用具体的例子 - 道歉。
    • 我有从他们在队列中的位置派生的乘客属性。例如乘客位置。要返回这个,Passenger 需要知道父队列。当然,我可以在 Queue 上有一个方法,获取参数并返回位置,但这似乎也很混乱
    • 当一个功能需要访问两个不同的对象,它们是一个层次结构的成员,决定将功能实现放入哪个类型可能是一门艺术......这两种类型的相对位置是该决定的一个(但不一定是驱动因素)因素。
    • 无论您将实现放入哪一个,当然可以在另一个中放入另一种实现方法,它代表第一个中的实际实现。
    【解决方案2】:

    双向关系往往很难正确实现。这里流行的建议应该是“不要那样做,很可能你实际上并不需要它,而且弊大于利。”

    如果经过深思熟虑,您认为双向关系是必要的,您可以设置Car 属性internal,它不能完全防止流氓设置不需要的值,但它确实限制了表面面积显着。

    【讨论】:

    • 我得出了同样的结论。查看 MS Word 和 MS Project 的 API,所有这些对象中都有父/子关系。当然,作为 API 的用户,许多属性对我来说是只读的,但可能不是内部的。
    • 认识几个从事原始 MSProject 的人,我不一定认为它的设计和对象模型代表“最佳实践”。 ;)
    【解决方案3】:

    您可能需要考虑使用一个仅设置 Wheel.parentCar 的设置器,前提是该设置为空,但前提是您能够假设您的第一个设置有效并因此能够忽略任何其他尝试。

    编辑:但是,这是添加Car 对象的合适位置。您还可以进行检查以创建(例如)Wheel.validateCar(Car carInQuestion),它强制仅在Car 中存在当前Wheel 对象的位置设置parentCar 属性。这意味着您将拥有一个公共方法,用于根据轮子的特定实例搜索 Car.wheels 的成员资格。但只有当你真的觉得有必要严格时。

    【讨论】:

    • 这不是一个坏主意。但这有点暗示你可以改变父母,即使你不能
    • 不一定;但是,您可以抛出错误或处理它。实际上,实现和语义取决于您,以及您希望如何让外部对象与之交互 - 但使用 setter 有条件地分配属性是合法的。
    • 是的,这样可以保护它。但是,如果车轮从汽车上拆下,那么它就无法解除它。
    • 按照同样的逻辑,您可以使用Wheel.validateCar(Car carInQuestion) 来有效地处理设置和可能取消设置;它显然是一个可以访问内部属性的方法,因此以 Wheel.parentCar 作为参数调用它,您可以根据上下文处理这两个操作。
    • 我承认这可能不是最好的设计选择,但是如果您必须限制访问/可见性,那么您没有太多好的选择而不牺牲任何一个简单或安全。
    【解决方案4】:

    对我来说,Wheel 对象的 Car 属性可写是有意义的,因为没有什么可以阻止您将 Wheel 从一个 ToyotaCamry(Car 的子类)移动到另一个。虽然您可能希望在允许调用 Car.Drive() 之前在原车上拥有一个 Drivable 属性来检查 List.Count 是否仍为 4。

    你用汽车制造车轮吗?如果是这样设置当时的属性。否则在 Wheel 连接到 Car 时设置它。

    【讨论】:

    • 是的,您可以将车轮从一个移动到另一个,但那辆车怎么知道车轮已被移动?
    • 每个 Car 对象都需要一些容器来计算附加的 Wheels。请参阅我的关于检查 Drivable-ity 的编辑。可能更复杂,因为后轮和前轮可能不同。有 10 个轮子的汽车等。
    • @Mark 909 - 不要忘记备用
    • 是的,我需要在 Car 中收集一个集合,但问题是保持这个集合和汽车的父属性同步
    • @Mark 909 - 集合的管理和零件上的任何所有权属性都应该封装在您的 Car AddPartRemovePart 方法中,我会想到
    【解决方案5】:

    问题:Wheel 实例可以稍后分配给不同的 Car 实例吗?

    1) 如果是,则公开一个公共方法来设置 parentCar。您可以使其成为一种流畅的方法,以简化代码:

    public class Wheel 
    {
        private Car parentCar;
    
        public ParentCar 
        {
            get
            {
                return parentCar;
            }
        }
    
        public void SetParentCar(Car _parentCar)
        {
            parentCar = _parentCar;
            return this;
        }
    }
    

    在您分配的地方添加轮子,进行分配:

    void AddWheel ( Wheel WheelToAdd)
    {
        wheels.Add(WheelToAdd.SetParentCar(this));
        //Some Other logic relating to adding wheels here
    }
    



    2)如果没有,只需在构造函数中设置父级即可。

    public class Wheel 
    {
        private Car parentCar;
    
        public ParentCar 
        {
            get
            {
                return parentCar;
            }
        }
    
        public Wheel(Car _parentCar)
        {
            parentCar = _parentCar;
        }
    }
    

    【讨论】:

      【解决方案6】:

      我会选择“不要”。轮子不仅仅是Car 的属性。轮子可以用在TrailerCartFerrisWheel 上(好吧,也许这有点牵强)。关键是,通过使使用车轮的汽车成为车轮本身的属性,您将车轮的设计与在汽车上使用相结合,此时,您将失去该类的任何可重用性。

      在这种情况下,让轮子知道它的实际用途似乎会失去很多,得到的却很少。

      【讨论】:

        【解决方案7】:
        【解决方案8】:

        可能有点偏左,但从概念上讲,车轮是汽车的“一部分”。您是否考虑过使用部分类?乘客是队列的“一部分”。我发现使用部分类和定义良好的接口在某些场景中非常有用。您可以将整个对象的各个方面抽象为接口,并将每个接口实现为部分类。结果是一个完整的对象,可以通过其部分接口进行抽象以满足一系列合同场景。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-05-07
          • 2014-02-26
          • 1970-01-01
          • 2018-10-03
          • 1970-01-01
          相关资源
          最近更新 更多