2012-09-08 38 views
3

我本来打算写:在Objective-C中,覆盖合成setter的正确方法是什么?

-(void) setLeftChild:(NSNode *) leftChildNode { 

    _leftChildNode = leftChildNode; 
    leftChildNode.parent = self; 
    // ... 
} 

设置所有适当的值节点的孩子,和子节点的父节点应指向回到自我,和子节点的树的对象应该是一样的父节点的树对象(节点所属的树)。

但后来我意识到这样做会影响简单的线node.leftChild = something;所以这条线会有“副作用”,否则可能是意想不到的。

而且,如果setLeftChild实际使用的其他财产的setter(如使用leftChildNode.parent = self,这是正确的接口),并依次那些制定者使用该节点的leftChild二传手 - 它可以成为一个无限循环这种方式。如果一些特殊情况导致这个无限循环,这将不是那么好,并且您总是需要担心潜在的无限循环。

另一种方式是从来没有覆盖任何合成的二传手,但只使用名称addLeftChildNode而不是setLeftChildNode,和里面addLeftChildNode,我们可以无后顾之忧自由使用属性setter。但是如果我们真的想要“设置”它 - 如果名称是“add”,那么它可能意味着只有在它为空时才添加它,所以名称“set”更合适。如果我把它叫做addLeftChild而不是addLeftChildNode,那么这两个名字很混乱。也许如果使用了一个约定,比如称为setAndConfigLeftChildNode,那么它不会混淆,显然不会覆盖setter,并且可能是代码中遵循的约定。 (使用setAndConfig,而不是覆盖的setter)

所以我们的问题:一个重写的二传手

1)意想不到的副作用(在问题的开始)
2)无限循环
3 )方法的命名,可能会引起混淆

是否有一个很好的通用/最佳实践?

+0

'setLeftChild:'的第一行应该是'_leftChild = leftChildeNode'? –

回答

3

好问题......它确实归结为偏好,但我想指出,你也可以用下面的改变setter函数的名称:

@property (setter=assignLeftChildNode) NSNode *leftChildNode; 

所以,你必须多一点灵活性关于命名约定...... setLeftChildNode不必由合成的setter进行。我知道这不是对你的问题的直接回答,但我希望这有助于。

+0

如果这不是一个答案,并得到了一些upvotes,那么这个问题可能会在接下来的几个月被忽视...... –

+3

一般来说,使用'getter'或'setter'属性注释是不好的。对于'BOOL'来说,做'isProperty'可以,但是由于其他原因,我会非常谨慎。整个键值编码/观察框架期望这些名称能够成立,更重要的是其他开发人员期望这些名称能够成立。在我们甚至不知道的某个框架中,可能有些东西依赖于那些方法。请不要认为违反命名惯例是个人偏好的一些无后果表达。 –

1

除了命名约定(已经在您的其他问题中讨论过)之外,我不会重写setter函数,因为(正如您已经注意到的)副作用不容易监督。我会用一种分离的方法,例如setLeftTree:

如果您的节点已经有一个左孩子和你设置一个新的,你也应该考虑到以前孩子的家长设置为nil

- (void)setLeftTree:(NSNode *)leftChildNode 
{ 
    if (self.leftChild != nil) 
     self.leftChild.parent = nil; 
    self.leftChild = leftChildNode; 
    leftChildNode.parent = self; 
} 
2

我认为它的罚款有一定的毒 - 在某些情况下,在自定义设置器中产生影响。例如,我经常想要一个对象使用KVO来监视我需要经常切换的另一个对象的属性。在您的情况下,数据结构的完整性将受到影响,除非这些操作是在设置属性时执行的。所以对我来说,在你的二传手中做到这一点很有意义。我认为这只是常识。任何真正发生时,只要此属性发生变化应该发生在setter,否则你会忘记一些时间

无限递归是一个真正的危险,所以重写setter应该小心谨慎。你会想在这里练习一些防守编码。

避免令人困惑的方法名称是至关重要的,尤其是在以此为中心的以命名约定为中心的平台中。你是正确的考虑到这一点。

总的来说,我认为你在这里是正确的。这是一个很好的例子,说明什么时候在你的二传手中产生副作用,我认为你的命名选择是合适的。出于对无限递归的谨慎考虑,您可能希望在此方法内直接使用实例(即_leftChild而不是​​)。

+0

我比你更喜欢你的答案(+1)。 - 但是,使用'setParent:'维护完整性是很困难的,因为您不知道是否更新父级的左侧或右侧子节点。一个可能的解决方案是在公共接口中将“parent”设置为只读属性,并仅在实现中使用它。 –

+0

谢谢@MartinR!在完整性问题上抓住并解决问题! –

1

想过这一点我想答案是关系需要完全从一侧控制。鉴于您的子节点的parent属性可能是weak以停止保留周期,我们将指定父母为关系的所有者。最简单的表达方式是设置父级的方法对类是私有的(NB不称为类NSNode,NS前缀由Apple保留)。它不应该被称为setParent,因为我们不希望-setValue:forKey:在它上面工作或点符号。这种方法只是设置父级伊娃。所有与维护参照完整性有关的逻辑应该在setLeftChild:(和可能的setRightChild:)方法中。您的实现将是这样的:

// Assuming you are using ARC 
@interface MyNode() 

-(void) privateSetParent: (MyNode*) newParent; 

@end 

@implementation MyNode 
{ 
    _weak MyNode* _parent; 
    MyNode* _leftTree; 
    MyNode* _rightTree; 
} 

-(void) privateSetParent: (MyNode*) newParent 
{ 
    _parent = newParent; 
} 

-(void) setLeftTree: (MyNode*) newTree 
{ 
    [[newTree parent] remove: newTree]; 
    [_leftTree privateSetParent: nil]; 
    _leftTree = newTree; 
    [newTree privateSetParent: self]; 
} 

-(void) remove: (MyNode*) subTree 
{ 
    if (_leftTree == subTree) 
    { 
     _leftTree = nil; 
     [subTree privateSetParent: nil]; 
    } 
    if (_rightTree == subTree) 
    { 
     _rightTree = nil; 
     [subTree privateSetParent: nil]; 
    } 
} 

@end 

你仍然可以有一个只读parent财产,你仍然可以做志愿与manual change notification

相关问题