3

我原本打算写:

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

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

为节点的子节点设置所有合适的值,子节点的父节点应该指向自身,子节点的树对象应该和父节点的树对象(节点所属的树)相同。

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

此外,如果setLeftChild实际使用其他属性的 setter(例如 using leftChildNode.parent = self,这是正确的接口),而这些 setter 又使用节点的leftChildsetter 怎么办——这样会变成无限循环。如果某些特殊情况导致了这个无限循环,那就不是很好了,你总是要担心潜在的无限循环。

另一种方法是永远不要覆盖任何合成的setter,而只需使用名称addLeftChildNode而不是setLeftChildNode,在里面addLeftChildNode,我们可以无忧无虑地自由使用属性setter。但是如果我们真的想“设置”它怎么办——“添加”这个名字可能意味着只有当它为空时才添加它,所以“设置”这个名字更合适。如果我将其称为addLeftChild而不是addLeftChildNode,那么这两个名称会令人困惑。也许如果使用约定,例如调用它setAndConfigLeftChildNode,那么它不会令人困惑并且显然不会覆盖 setter,并且可以成为代码中遵循的约定。(使用setAndConfig而不是覆盖设置器)

所以我们有以下问题:

1) 被覆盖的 setter 的意外副作用(在问题的开头)
2) 无限循环
3) 方法的命名,可能会造成混淆

有没有好的通用/最佳实践?

4

4 回答 4

3

好问题......它确实归结为偏好,但我想指出您也可以使用以下内容更改 setter 函数的名称:

@property (setter=assignLeftChildNode) NSNode *leftChildNode;

所以你在命名约定方面有更多的灵活性......setLeftChildNode不必由综合设置器采用。我知道这不是您问题的直接答案,但我希望这会有所帮助。

于 2012-09-08T04:12:44.537 回答
2

我认为在某些情况下,在自定义设置器中产生一些副作用是可以的。例如,我经常希望一个对象使用 KVO 来监视另一个我需要经常切换的对象的属性。在您的情况下,数据结构的完整性将受到损害,除非这些操作是在设置属性时准确执行的。所以我觉得在你的二传手里做这些是有意义的。我认为这只是常识。每当此属性更改时,真正必须发生的任何事情都应该发生在 setter 中,否则您会忘记一些 time

无限递归是一个真正的危险,因此绝对应该小心地覆盖 setter。您需要在此处实践一些防御性编码。

避免混淆方法名称至关重要,尤其是在像这样以命名约定为中心的平台中。考虑到这一点是正确的。

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

于 2012-09-08T07:06:46.423 回答
1

稍微考虑一下,我认为答案是需要完全从一侧控制这种关系。鉴于parent您的子节点的属性可能weak是停止保留循环,我们将指定父节点作为关系的所有者。最简单的表达就是设置父类的方法是类私有的(NB不要调用类NSNode,NS前缀是苹果保留的)。不应该调用它,setParent因为我们不想-setValue:forKey:使用它或点符号。此方法只是设置父 ivar。与维护引用完整性有关的所有逻辑都应该在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属性,并且您仍然可以使用手动更改通知进行 KVO 。

于 2012-09-08T09:50:43.917 回答
1

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

如果您的节点已经有一个左子节点并且您设置了一个新节点,您还应该考虑将前一个子节点的父节点设置为nil

- (void)setLeftTree:(NSNode *)leftChildNode
{
    if (self.leftChild != nil)
        self.leftChild.parent = nil;
    self.leftChild = leftChildNode;
    leftChildNode.parent = self;
}
于 2012-09-08T06:27:45.437 回答