我有以下问题,我想知道是否有一种很好的方法可以在不使用多重继承的情况下对这些对象进行建模。如果有什么不同,我正在使用 Python。
学生需要联系信息和学生信息。成人需要联系信息和账单信息。学生可以是成年学生,在这种情况下我需要联系/学生/账单信息,或者他们可以是儿童,在这种情况下我需要联系/学生/家长信息。
只是为了清楚系统将如何使用,我需要能够要求一份所有成年人的名单(我会得到成年学生加上父母),或所有学生的名单(我会得到儿童学生加上成人学生)。
此外,所有这些对象都需要有一个共同的基类。
我有以下问题,我想知道是否有一种很好的方法可以在不使用多重继承的情况下对这些对象进行建模。如果有什么不同,我正在使用 Python。
学生需要联系信息和学生信息。成人需要联系信息和账单信息。学生可以是成年学生,在这种情况下我需要联系/学生/账单信息,或者他们可以是儿童,在这种情况下我需要联系/学生/家长信息。
只是为了清楚系统将如何使用,我需要能够要求一份所有成年人的名单(我会得到成年学生加上父母),或所有学生的名单(我会得到儿童学生加上成人学生)。
此外,所有这些对象都需要有一个共同的基类。
您所拥有的是角色的示例——通过继承对角色进行建模是一个常见的陷阱,但角色可以更改,并且不建议更改对象的继承结构(即使在可能的语言中,如 Python)。孩子长大成人,一些成年人也将成为儿童学生的父母以及成年学生自己——他们可能会放弃其中一个角色,但需要保留另一个角色(他们的孩子换了学校,但他们没有,反之亦然) .
只要有一个类 Person 具有必填字段和可选字段,后者代表角色,可以更改。“请求列表”(完全独立于继承或其他方式)可以通过动态构建列表(遍历所有对象以检查每个对象是否满足要求)或维护与可能要求相对应的列表(或频繁查询和即席查询的两种策略的混合)。某种数据库在这里可能会有所帮助(并且大多数数据库在没有继承的情况下工作得更好;-)。
我确信其他人很快就会发表评论(如果他们还没有评论的话),一个好的 OO 原则是“优先组合而不是继承”。从您的描述来看,这听起来像是您违反了Single Responsibility Principle,应该将功能分解为单独的对象。
我还想到 Python 支持鸭子类型,这就引出了一个问题:“为什么所有类都有一个共同的基类如此重要?”
非常简单的解决方案:使用组合而不是继承。与其让 Student 从 Contact 和 Billing 继承,不如让 Contact 成为 Person 的字段/属性并从中继承。使计费成为学生的一个领域。使 Parent 成为 Person 的自引用字段。
听起来您并不需要多重继承。事实上,你并不需要多重继承。这只是多重继承是否简化事情的问题(我在这里看不到这种情况)。
我将创建一个 Person 类,其中包含成人和学生将共享的所有代码。然后,您可以拥有一个包含只有成人需要的所有东西的 Adult 类和一个包含只有孩子需要的代码的 Child 类。
这听起来像是可以使用组件架构(如 zope.components)很好且灵活地完成的事情。组件在某种程度上是一种超级灵活的组合模式。
在这种情况下,当您加载数据以根据某些信息在其上设置标记界面时,我可能最终会做一些事情,例如如果年龄 >= 18 您设置了 IAdult 界面等。然后您可以通过以下方式获取成人信息正在做
adultschema = IAdultSchema(person)
或类似的东西。(编辑:实际上我可能会使用
queryAdapters(person, ISchema)
一口气获得所有模式。:)
组件架构可能有点矫枉过正,但一旦你习惯了这样的想法,许多问题就会变得微不足道。:)
查看 Brandons 出色的 PyCon 谈论它:http ://www.youtube.com/watch?v=UF77e2TeeQo 我的介绍博客文章:http ://regebro.wordpress.com/2007/11/16/a-python-组件架构/
我认为您的要求过于简单,因为在实际情况下,即使他们是需要家长联系信息的未成年人,您也可能让学生拥有自己的帐户来处理账单。此外,您的父母联系信息可能与实际情况下的账单信息不同。您可能还需要与其他人一起收费的成年学生。但是,除此之外 - 看看您的要求,这是一种方法:
类:Person、BillingInfo、StudentInfo。
所有人都是 Person 类的实例...
class Person:
# Will have contact fields all people have - or you could split these off into an
# object.
parent # Will be set to None for adults or else point to their parent's
# Person object.
billing_info # Set to None for non-adults, else to their BillingInfo object.
student_info # Set to None for non-student parents, else to their StudentInfo
# object.
检查字段将允许您根据需要创建列表。
一种解决方案是创建一个基础 Info 类/接口,ContactInfo、StudentInfo 和 BillingInfo 类继承自该类/接口。拥有某种包含 Info 对象列表的 Person 对象,然后您可以使用 ContactInfo、StudentInfo 等填充 Info 对象列表。
在伪代码中,您可以执行以下操作:
Class Student
Inherits WhateverBase
Private m_StudentType as EnumStudentTypes 'an enum containing: Adult, Child
Private m_Billing as Billing
Private m_Contact as Contact
Private m_Parent as Parent
Public Sub Constructor(studentType, billing, contact, parent)
...logic to make sure we have the right combination depending on studentType.
...throw an exception if we try to assign a a parent to an adult, etc.
...maybe you could have seperate constructors, one for each studenttype.
End Sub
Public Property StudentType as EnumStudentTypes
Get
Return m_StudentType
End Get
End Sub
Public Property Parent
Get
...code to make sure we're using a studentType that has a parent,
...and throws an exception if not. Otherwise it returns m_Parent
End Get
End Sub
[more properties]
End Class Student
然后你可以创建一个名为 StudentManager 的类:
Public Class StudentManager
Public Function GetAdults(studentCollection(Of Students)) as StudentCollection(Of Students)
Dim ResultCollection(Of Students)
...Loop through studentCollection, adding all students where Student.StudentType=Adult
Return ResultCollection
End Function
[Other Functions]
End Class
Public Enum StudentType
Adult=0
Child=1
End Enum