我想问一个关于你将如何处理一个简单的面向对象设计问题的问题。 我有我自己的什么应对这种情况的最好办法,但我会有兴趣听从堆栈溢出社会一些观点的一些想法。 链接到相关的网上文章也很受青睐。 我使用C#,但问题是不特定的语言。
假设我写一个视频商店应用程序,其数据库中有一个Person
表,与PersonId
, Name
, DateOfBirth
和Address
字段。 它也有一个Staff
表,其中有一个链接到一个PersonId
和Customer
表也链接到PersonId
。
一个简单的面向对象的方法是说,一个Customer
“是” Person
,因此创建类有点像这样:
class Person {
public int PersonId { get; set; }
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
public string Address { get; set; }
}
class Customer : Person {
public int CustomerId { get; set; }
public DateTime JoinedDate { get; set; }
}
class Staff : Person {
public int StaffId { get; set; }
public string JobTitle { get; set; }
}
现在,我们可以编写一个函数说电子邮件发送到所有的客户:
static void SendEmailToCustomers(IEnumerable<Person> everyone) {
foreach(Person p in everyone)
if(p is Customer)
SendEmail(p);
}
该系统运行正常,直到我们有别人谁既是客户和员工中的一员。 假设我们真的不希望我们everyone
列表,以便在同一个人两次,一次作为一个Customer
,一次作为Staff
,难道我们之间做出任意选择:
class StaffCustomer : Customer { ...
和
class StaffCustomer : Staff { ...
显然,只有前两个会不会打破SendEmailToCustomers
功能。
所以,你会做什么?
- 使
Person
类有一个可选的引用StaffDetails
并CustomerDetails
类? - 创建一个包含一个新的类
Person
,加上可选的StaffDetails
和CustomerDetails
? - 使所有的接口(如
IPerson
, IStaff
, ICustomer
),并创建实施适当的接口三类? - 采取另一种完全不同的方法?
Answer 1:
马克,这是一个有趣的问题。 你会发现在这许多的意见。 我不相信这是“正确”答案。 这是其中一个刚性heirarchial对象设计能真正引起问题的系统建成后一个很好的例子。
例如,假设你的“客户”和“员工”班去了。 您部署系统,一切都是幸福的。 几个星期后,有人指出,他们都是“员工”和“客户”,他们没有得到客户的电子邮件。 在这种情况下,你有大量的代码修改,使(重新设计,不重因子)。
我相信,如果你尝试有一组实现所有排列和人与自己的角色相结合的派生类,这将是过于复杂,难以维护。 这是尤其如此鉴于上述例子很简单 - 在大多数实际应用中,事情会更加复杂。
因为在这里你的榜样,我会去的“再举一个完全不同的方法。” 我将实现Person类,并在其中包括“角色”的集合。 每个人都可以有一个或多个角色,如“客户”,“工作人员”和“供应商”。
这将使它更容易添加角色新的要求被发现。 例如,你可能只是一个基地“角色”级,并从中获得新的角色。
Answer 2:
您可能要考虑使用党和问责模式
这样,人都会有问责的集合,它可以是类型客户或员工。
如果以后添加更多的关系类型的模式也将更加简单。
Answer 3:
纯的做法是:使所有的接口。 作为实现细节,你可以选择使用任何的各种形式的构成或实现继承。 由于这些是实施细节,它们都不重要,以你的公共API,所以你可以自由选择任何使您的生活简单。
Answer 4:
一个人是一个人,而客户就是这样一个人可以采取不时角色。 男人和女人会候选人继承人,但客户是不同的概念。
Liskov替换原则是我们必须能够使用派生类,我们有一个基类的引用,不知道这件事。 有客户继承个人违反本。 客户可能或许也可以由组织发挥了作用。
Answer 5:
让我知道如果我理解正确的Foredecker的答案。 这里是我的代码(在Python,对不起,我不知道C#)。 唯一的区别是,如果一个人“是一个客户”我不会通知什么的,我会做,如果他的角色之一“感兴趣”说事儿。 难道这还不够灵活?
# --------- PERSON ----------------
class Person:
def __init__(self, personId, name, dateOfBirth, address):
self.personId = personId
self.name = name
self.dateOfBirth = dateOfBirth
self.address = address
self.roles = []
def addRole(self, role):
self.roles.append(role)
def interestedIn(self, subject):
for role in self.roles:
if role.interestedIn(subject):
return True
return False
def sendEmail(self, email):
# send the email
print "Sent email to", self.name
# --------- ROLE ----------------
NEW_DVDS = 1
NEW_SCHEDULE = 2
class Role:
def __init__(self):
self.interests = []
def interestedIn(self, subject):
return subject in self.interests
class CustomerRole(Role):
def __init__(self, customerId, joinedDate):
self.customerId = customerId
self.joinedDate = joinedDate
self.interests.append(NEW_DVDS)
class StaffRole(Role):
def __init__(self, staffId, jobTitle):
self.staffId = staffId
self.jobTitle = jobTitle
self.interests.append(NEW_SCHEDULE)
# --------- NOTIFY STUFF ----------------
def notifyNewDVDs(emailWithTitles):
for person in persons:
if person.interestedIn(NEW_DVDS):
person.sendEmail(emailWithTitles)
Answer 6:
我将避免“是”检查(“的instanceof”在Java中)。 一个解决方案是使用一个装饰图案 。 你可以创建一个装饰的人在那里EmailablePerson使用成分来保持状态的人士和代表所有非电子邮件的方法来Person对象的私有实例的EmailablePerson。
Answer 7:
我们去年研究在大学这个问题,我们正在学习艾菲尔所以我们用多重继承。 反正Foredecker角色替代,似乎有足够的灵活性。
Answer 8:
什么是错在发送电子邮件给客户谁是工作人员? 如果他是一个客户,那么他可以发送的电子邮件。 我错在这样想? 你为什么要采取“大家”作为您的电子邮件列表? Woudlnt它是最好有一个客户名单,因为我们面对的是“sendEmailToCustomer”的方法,而不是“sendEmailToEveryone”的方法? 即使你想用“大家”列表中,您不能让该列表中重复。
如果这些都不是有很多redisgn的实现,我会去的第一Foredecker答案,可能是你应该有一个分配给每个人一定的作用。
Answer 9:
你的类只是数据结构:他们都没有任何问题,只是getter和setter。 继承是不恰当的在这里。
Answer 10:
采取另一种完全不同的方法:用类StaffCustomer的问题是,您的员工的成员都可以像刚才的工作人员开始并成为客户以后,所以你将需要删除它们为员工创造StaffCustomer类的新实例。 也许员工类的isCustomer“内一个简单的布尔将使我们每个人名单没有得到工作人员,因为它会知道它已被作为客户包括(大概是从让所有的客户,并从相应的表中的所有工作人员编制)。
Answer 11:
这里的一些技巧:从类别“甚至不认为这做”这里有一些代码坏榜样遇到:
Finder方法返回对象
问题:根据出现的次数发现取景器方法返回表示出现的次数一个数字 - 或者! 如果只有一个发现返回的实际对象。
别这样! 这是最糟糕的编码实践之一,它引入了模糊和食堂的方式,在不同的开发者进场,她或他会恨你这样做的代码。
解决方法:如果有需要这样2个功能:计数,取一个实例做创建2个方法中的一种,它返回的计数和一个返回的实例,但从来没有一个单一的方法做的两种方式。
问题:一个衍生不好的做法是,当取景器方法将返回的一个单次出现发现任一发生的阵列如果发现不止一个。 这种惰性编程风格做是通过谁做前面的一个一般的程序员很多。
解决方案:有了这个在我的手上我会返回长度为1,如果只有一个发生被找到(一个)的阵列,并与长度的阵列> 1,如果发现了更多的事件。 此外,发现没有出现在所有将返回空值或取决于应用长度为0的数组。
面向接口编程和使用协变返回类型
问题:面向接口编程和使用协变返回类型和调用代码铸造。
解决方案:使用而不是在接口中定义的相同的超类型用于限定其应指向返回值的变量。 这样可以使面向接口编程方法和你的代码干净。
有超过1000行类是潜伏危险方法有超过100线是潜伏的危险呢!
问题:一些开发者在一个类/方法的东西太多太多的功能,是懒得打破功能 - 这会导致较低的凝聚力和可能高耦合 - 一个非常重要的原则,在OOP逆! 解决方法:避免使用过多的内/嵌套类 - 这些类要在每个需要的基础只用,你不必使用它们做一个习惯! 使用它们可能会导致更多的问题,例如限制继承。 了望代码重复的! 相同或过于相似的代码可以在一些实施父或可能在另一个类中已经存在。 如果是在另一个类,这是不是一个超也违反了凝聚力规则。 当心静态方法 - 也许你需要一个实用工具类,加入!
更多在: http://centraladvisor.com/it/oop-what-are-the-best-practices-in-oop
Answer 12:
你可能不希望使用继承这一点。 试试这个:
class Person {
public int PersonId { get; set; }
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
public string Address { get; set; }
}
class Customer{
public Person PersonInfo;
public int CustomerId { get; set; }
public DateTime JoinedDate { get; set; }
}
class Staff {
public Person PersonInfo;
public int StaffId { get; set; }
public string JobTitle { get; set; }
}
文章来源: Object Oriented Best Practices - Inheritance v Composition v Interfaces