即使我已经编程了很长一段时间,但是当谈到耦合对象时,我似乎总是把头撞到墙上,所以我想知道是否有人有任何资源或黄金规则我可以遵循。< / p>
让我举一个小例子,没有特别的语言......
class Person {
private int personnel_id
private String first_name;
private String last_name;
private int personnel_level;
//Lab labs[4]; <- Lab(s) the Person works in
}
class Lab {
private int lab_id;
private String lab_name;
//Person[99] personnel; <- Person(s) working in the Lab
}
现在让我们忽略ctors / setters / getters / dtors并只是实例化一些东西......
Person people = new Person[1500];
Lab labs = new Lab[10];
我的问题是......这里的最佳做法是什么......
people["Gordon Freeman"].blewUp((Lab)"Black Mesa");
-> returns T/F
...或
labs["BlackMesa"].blownUpBy((Person)"Gordon Freeman");
-> returns T/F
或者它甚至不重要:S
我正在研究的现实生活中的例子要复杂得多。每当Person
做某事时,Lab
中的每个人都需要得到通知等等,而我只想弄清楚我是否有任何可以应用的原则。
答案 0 :(得分:3)
我的答案是几个现有答案的组合。
这里的基本问题是这里有一个隐藏的概念。该方法实际上并不是关于实验室对象或人物对象,而是关于它们之间的关系。 (正如@dacris和@vs所建议的那样。)
处理此类情况的一种方法是使用双重发送的语言(谢谢,@ Ken。)
另一种方法是使用自动生成的代码(谢谢@vs。),在这种情况下,两种方向都可以使用方法。
但通常这些解决方案并不实用 - 改变整个语言似乎有点矫枉过正。
自动生成的解决方案为我们提供了一个洞察力。这两种技术都应该合法。所以你可以手动实现这两种技术。
但是,如果您不想重复自己,这种方法可以清楚地表明EITHER方向是合法的。所以不要冒汗,太多。
如果你正在编写一个系统,其中Person对象除了爆炸之外还有其他用途,那么耦合最好从Lab转到Person(即将方法放在Lab对象上),这样Person对象就可以了在其他地方使用,无需处理Lab对象或爆炸相关方法的更改。
......反之亦然。如果一个人所做的一切都在爆炸,那么逻辑应该在那里保持实验室清洁和原始(这对实验室来说很重要!)
答案 1 :(得分:2)
您可能想要了解一下Observer和Publish / Subscribe模式。您所描述的几乎是Observer模式的经典应用程序。发布/子模式基本上是相同的想法抽象了一点,以帮助缩放。
在任何情况下,鉴于这种模式已经众所周知,你可能也会遵循它的惯例,除非你遇到一种情况,你真的确信你会从中受益。
答案 2 :(得分:1)
想想你说英语。一般规则是,动词(和方法)应尽可能具有“主动语态” - 也就是说,对象应该做某事,而不是做一些事情。
如果这是一个事件,被动语态会更有意义 - 实验室应该知道人员在哪里,但是一些随机的人(即使是在同一实验室工作的人)可能不应该这样做,所以通知实验室爆炸最好来自实验室本身。但实际上,这是关于个人(或团队)偏好的情况。
答案 3 :(得分:1)
我不完全确定你的例子意味着什么,但是
Craig Larman Applying UML and Patterns是一本出色的书,其中包含你想要的东西。
这本书广泛谈到了分配责任。例如,您可以使用信息专家模式,在这种情况下,对所涉及变量有最多了解的对象将是负责使用该方法的对象。
答案 4 :(得分:1)
multiple dispatch的系统可以很好地避免此问题。例如,在Dylan中,您可能会说:
define method blows-up(p :: <person>, l :: <lab>) => explodes :: <boolean>;
// ...returns #f or #t...
end method;
(我链接到c2.com MultiMethods页面,因为我认为它描述的内容最不好。维基百科有一个Multiple_Dispatch页面,但它的例子非常糟糕。)
答案 5 :(得分:1)
oO为您提供了一个不同的视角:实际上您对人员或实验室不感兴趣,但他们之间的关系。如果从UML或数据库的角度来看,你会发现这种关系在你的(心理)模型中是一个非常新的概念。请参阅上面的@dacris评论,他在那里介绍了一个新课程。
如果您要使用ORM(对象关系映射),就像在使用UML模型进行工程设计时那样,那两个方法blowsUp()
和blownUpBy()
将自动进行代码生成,并分别使用它们运行时检查以确保它们的一致性。
Larman的书确实应该包含有关此主题的内容。
答案 6 :(得分:0)
我认为它与现实世界和编码惯例有关,而不是一般的良好做法。对于你的英语,我仍然更喜欢打电话给人们。通知(实验室)。但是,如果您希望您的实验室掌握一些关于谁调用它的数据,那么您可以做lab.isNotifiedBy(人)。
这里的好习惯是,你和你的同事在查看代码,理解它的作用,如果他们想要找到一个方法,他们知道应该从哪里开始而不仅仅是继续搜索或询问时,这是有道理的。你
答案 7 :(得分:0)
我喜欢设计这样的东西:
let blackMesa = labs["BlackMesa"]
if (blackMesa.isDestroyed)
{
let destroyer = blackMesa.destroyer
}
答案 8 :(得分:0)
在这种情况下,我想介绍一个新对象 - LabExplosion
class LabExplosion
{
private Person personResponsible;
private Lab labAffected;
}
然后,在某处保留LabExplosions的存储库,并执行以下操作:
// To find out if Gordon Freeman ever blew up Black Mesa
explosions.find("Gordon Freeman", "Black Mesa").length > 0;
// returns T/F
答案 9 :(得分:0)
这里的最佳做法是什么......
这取决于您的use case,用户将如何使用该系统?它会被Person
“吹”给实验室吗?或者系统的用例是Person
爆炸Labs
?
或者它甚至不重要:S
最后结果是一样的,但重要的是代码的语义。如果让人们吹嘘实验室听起来很傻,那么就不要这么做了。
因此,BobTurbo提到的黄金法则是找出系统中的“信息专家”(参见:GRASP)并将控制权交给该对象。
您通常会定义用户历史或叙述如何使用系统,例如,叙述是:
当一个人做某事时,必须通知实验室中的每个人。
然后,对我而言,这意味着Person
在Lab
中有效,当它被创建时,该人可能会收到他正在进行的实验室,并注册自己以通知所发生的事情。那个实验室。
由于实验室有要通知的人员名单,因此成为执行通知的实验室是有意义的(在这种情况下,人员控制实验室)
然后Person
可能被定义为:
labs.Person {
- name: String
- lab : Lab
+ Person( withLab: Lab , andName: String ) {
self.lab = withLab
self.name = andName
self.lab.subscribe( self ) // want to know what happens
}
+ blowUpLab() {
lab.boom!(blownBy:self)
}
// receive a lab even notification
// performed by "someone"
+ labEvent( lab:Lab, by: Person ) {
// if is my lab and it wasn't me?
if( self.labg .== lab .&& self .!= by ) {
// ok it was someone else....
}
}
}
因此,该人在实验室中做了一些事情,在这种情况下是公共方法blowUpLab
,它通过调用实验室的boom!
方法炸毁了该人的实验室。
反过来,Lab
执行方法操作并在最后通知所有订阅者:
labs.Lab {
- labName:String
- subscribers: Person[0..*]
+ subscribe( to: Person ) {
subscribers.add( to )
}
+ boom!( blowedBy: Person ) {
// do blow up the lab
....
// and then notify:
subscriber.forEach( person: Person ) {
person.labEvent( self, blowedBy )
}
}
}
这是观察者模式。
最后,您的主应用程序将创建人员和实验室并执行用例:
labs.MainApp {
_ main() {
blackMesaLab = Lab("BlackMesa")
gordon = Person( withLab: blackMesaLab, andName: "Gordon Freeman")
scott = Person( withLab: blackMesaLab, andName: "Scott Tiger")
james = Person( withLab: Lab("SO Labs"), andName:" James Hetfield");
persons = Array( gordon, scott, james )
....
while( true ) {
// every now and then, have someone blowing up it's lab
if ( randomNumber() .== 42 ) {
person.at( randomPosition ).blowUpLab()
}
}
}
}
这个主要应用程序,将创建三个人,与一些实验室,只有两个是相关的(斯科特和戈登)
随机其中一个将收到blowUpLab
消息,并将执行该方法。实验室将反过来通知该实验室的所有订户。
所以,当James Hetfield吹响它的实验室时,没有人会被通知:)
重点是 描述您的用例,并在那里识别信息专家;将控件赋予该对象,并让该对象将控件依赖于其他对象,但仅根据您的用例
我希望这是有道理的。