面向对象编程中处理双向关联的最佳方法是什么?

3
我希望知道处理面向对象编程中双向关联的最佳方式。我在Google和SO上找到了多种解决方案,但每一种似乎都有缺陷。语言不重要,但让我们用PHP来说明我的意思:
假设我有一个简单的州..[1..n]..城市关联:
public class State {
    public $cities;
    public function add_city($city) {}
}
public class City {
    public $state;
    public function set_state($state) {}
}

实施方案 #1:

public class State {
    public $cities;
    public function add_city($city) {
        $this->cities[] = $city;
        $city->state = $this;
    }
}
public class City {
    public $state;
    public function set_state($state) {
        $this->state = $state;
        $state->cities[] = $state;
    }
}

这种实现方法存在两个问题:

  • "$state"和"$cities"必须是公共的(这样任何人都可以添加城市而不需要使用公共函数add_city...)。大多数语言中没有“友元类”的概念。
  • 在添加之前,公共函数可能需要执行某些操作。

实现方法 #2:

public class State {
    public $cities;
    public function add_city($city) {
        $this->cities[] = $city;
        if ($city->state != $this) {
            $city->set_state($this);
        }
    }
}
public class City {
    public $state;
    public function set_state($state) {
        $this->state = $state;
        if (!in_array($this, $state->cities)) {
            $state->add_city($this);
        }
    }
}

比#1稍微好一点,但“set_state”函数必须调用“in_array”函数,大部分语言中这是O(n)的操作(将一个快速的O(1)操作转换为O(n))。

实现方法#3:

public class State {
    public $cities;
    public function add_city($city, $call_the_other_function = true) {
        $this->cities[] = $city;
        if ($call_the_other_function) {
            $city->set_state($this, false);
        }
    }
}
public class City {
    public $state;
    public function set_state($state, $call_the_other_function = true) {
        $this->state = $state;
        if ($call_the_other_function) {
            $state->add_city($this, false);
        }
    }
}

第三种实现方法非常有效,但由于额外的可选参数而有点“丑陋”(缺乏更好的术语),

无论如何,如果有人知道“正确的方式”(tm)是什么,我想知道。

编辑: 如果可能的话,我希望有一个解决方案:

  • 不使用另一个类
  • 不知道对象创建的顺序(即不是“构造函数”解决方案)

2
“Right Way(tm)”问题不适合在Stackoverflow上讨论,因为它们往往会产生基于个人观点的答案。请查看http://stackoverflow.com/help/on-topic以了解您应该和不应该询问的内容。 - Tim Lewis
请原谅我,但是我对你的第一个实现感到困惑。为什么这些变量需要是公共的?“友元”类不可以直接使用另一个类的公共方法吗? - Jacob
除了@TimLewis所说的,也许你的问题更适合于程序员StackExchange :) - Jacob
是的,但这样一来,“外部人员”就可以直接从集合中访问“add”函数,即$my_state->get_cities()->add()(绕过$my_state->add_city()中的“附加代码”...)。 - d08z
我相信没有正确的方式或完美的代码。要么某些东西适合你,要么不适合。如果不适合,就可以重构,故事结束。 - rr-
显示剩余2条评论
2个回答

2

我建议你使用构造函数,这样当你实例化一个城市时,可以直接传递其状态。

public class State {
    private $cities;
    public function add_city($city) {
        $this->cities[] = $city;
    }
}

public class City {
    private $state;
    function __construct($state) {
        $state->add_city($this)
        $this->state=$state
    }
}

如果在创建城市时状态尚不存在(将稍后创建并链接),该怎么办? - d08z
1
你需要明确你的使用情况,然后编写代码。 - NicolaSysnet
城市和州之间存在依赖关系:一个城市属于且仅属于一个州,一个州可以包含零个或多个城市。这种依赖关系要求在创建城市之前必须先创建其所属的州。答案用代码表达了这个语句,并提供了属性的正确封装。 - axiac
正如@axiac所指出的那样,这不仅仅是用例的问题,还涉及到类图:D - NicolaSysnet

1
在你的所有提案中,城市知道州或反之亦然。如果您引入第三个类来负责将城市连接到州,例如LocationService,并使用一个方法linkCityToState,那会怎样呢?这样,您以后可以通过linkCityToCountry或一些高级逻辑,如getPostalCodeFromApi来扩展它。
如果您担心性能问题,请将列表转换为哈希集,这将将查找复杂度降至O(log n)
此外,在您的实现#1中,您有city->cities...?
无论如何,除非我正在编写压缩算法、驱动程序、大量数据库查询等,否则我永远不会选择解决方案#3。

我的错误;我应该在我的问题中加上“不使用任何额外类”的限制。 - d08z
好的,循环引用通常通过引入第三个类来解决。 - rr-
诚实的问题:你为什么不会使用解决方案#3? - d08z
你正在浏览随机代码,看到一个方法:add_city()。它接受City参数,很完美。但是它还有一个布尔类型的参数 = WTF1。然后你看到它的名称:call_the_other_function = WTF2... 这违反了几个设计原则 - KISS、SRP,并且是过早优化的例子。如果你真的需要这么高的性能,不要使用PHP。 - rr-
但这就是为什么该参数是可选的,并且默认情况下为true,因此您不必在原始调用中包含它...无论如何,感谢您的一切。 - d08z

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接