[英]Implementing a bidirectional association relationship in Python
在 Martin Fowler 的 UML Distilled 的“双向关联”部分,他说:
在编程语言中实现双向关联通常有点棘手,因为您必须确保两个属性保持同步。 使用 C#,我使用以下代码来实现双向关联:
书中的代码
class Car...
public Person Owner {
get {return _owner;}
set {
if (_owner != null) _owner.friendCars().Remove(this);
_owner = value;
if (_owner != null) _owner.friendCars().Add(this);
}
}
private Person _owner;
...
class Person ...
public IList Cars {
get {return ArrayList.ReadOnly(_cars);}
}
public void AddCar(Car arg) {
arg.Owner = this;
}
private IList _cars = new ArrayList();
internal IList friendCars() {
//should only be used by Car.Owner
return _cars;
}
....
问题 1:
我尝试在 python ( get_cars()
in Person
&& get_owner_v2()
in Car) 中实现这个,我想知道我的代码是否可以用于描述“双向关联”,如果不能,应该如何修改为这样做?
注意:第一个版本(检查调用者的类/对象)工作正常,直到我开始独立创建汽车对象并分两步将它们分配给所有者(最后 4 个打印语句证明了这一点)。 第二个版本使用许可证号lno
来确定所有者。 不确定我是否做对了,但根据我的理解,它确实有效。
我的实现:
#trying the inverse-bidirectional association
class Person:
cars_and_lnos = []
def __init__(self,name):
self.__cars = []
self.__cars.append("Dummy")
self.__name = name
def add_car(self, *args, obj = None):
# import inspect
if not obj:
car_object = Car(*args)
else:
car_object = obj
Person.cars_and_lnos.append((car_object,self.__name))
self.__cars.append(car_object)
def __repr__(self):
return f"{self.__name}"
def get_cars(self):
return self.__cars
class Car:
car_count = 0
def __init__(self, lno, price ,year, make, model):
import inspect
self.__lno = lno
self.__price = price
self.__year = year
self.__make = make
self.__model = model
Car.car_count += 1
self.__car_id = Car.car_count
if "self" in inspect.getargvalues(inspect.stack()[1][0]).args :
self.__owned_by = f"Car (ID:{self.__car_id}) is Owned By: {inspect.stack()[1][0].f_locals['self']}, which is an instance of Class: {inspect.stack()[1][0].f_locals['self'].__class__.__name__}"
else:
self.__owned_by = "This car is not owned by anyone."
def __repr__(self):
return f"Car ID: {self.__car_id}."
def get_specs(self):
print(f"+{'-'*30}+")
print(f"""
Liscense No.: {self.__lno}
Price: {self.__price}
Year: {self.__year}
Make: {self.__make}
Model: {self.__model}
""")
@property
def get_lno(self):
return self.__lno
def get_owner_v1(self):
# import inspect
return self.__owned_by
def get_owner_v2(self):
if Person.cars_and_lnos:
for tup in Person.cars_and_lnos:
if self.__lno == tup[0].get_lno:
return f"Car (ID: {self.__car_id}) is owned by: {tup[1]}, he is a: {Person.__name__} Class."
return "[0] This car is not owned by anyone."
else:
return "[1] This car is not owned by anyone."
owner1 = Person("William")
owner1.add_car("4567781",10000,2012,"Toyota","Corrolla")
owner1.add_car("2137813",8000,2010,"Porshe","GT3")
owner1.get_cars()[1].get_owner_v1()
print(f"{owner1} owns {len(owner1.get_cars())-1} Car(s).")
print(owner1.get_cars()[1].get_owner_v1())
print("=====================================================")
owner2 = Person("Defoe")
owner2.add_car("8729120",10000,2012,"Dodge","Challenger")
print(f"{owner2} owns {len(owner2.get_cars())-1} Car(s).")
print(owner2.get_cars()[1].get_owner_v1())
print("=====================================================")
car1 = Car("7839291",10000,2012,"Chevrolet","Camaro")
car2 = Car("6271531",10000,2012,"Ford","Mustang")
print(car2.get_owner_v1())
print("=====================================================")
owner3 = Person("Lyan")
owner3.add_car("656721",9000,2013,"Toyota", "Camry")
owner3.add_car("652901",9000,2013,"Nissan", "Sunny")
owner3.add_car("870251",9000,2013,"BMW", "6 Series")
print(owner3.get_cars()[1].get_owner_v2())
print(owner2.get_cars()[1].get_owner_v2())
print(owner1.get_cars()[1].get_owner_v2())
print("=====================================================")
car3 = Car("5424201",10000,2012,"Volks","Eos")
print(car3.get_owner_v1())
print(car3.get_owner_v2())
owner4 = Person("Daphne")
owner4.add_car(obj=car3)
print(car3.get_owner_v1())
print(car3.get_owner_v2())
问题2:
在本节中,他说这两个符号可能相同:
下面的符号(没有指定任何箭头)是否也可以被认为是双向关系?
编辑:
我知道对类图的逻辑改进将是 * 到 *(多对多),但我只关心我对描述/设计的实现有多正确,如何改进,以及哪里不 -箭头关联线适合图片。
问题2已经完美回答了。 因此,在关注问题 1 之前,我将仅提供一些有关适航性的补充信息。
可导航性是在 UML 规范中以非常广泛的术语定义的:
可导航性意味着可以从关联另一端的实例有效地访问运行时参与链接的实例(关联的实例)。 实现这种高效访问的精确机制是特定于实现的。 如果一端不可导航,则从另一端访问可能会也可能不会,如果是,则可能效率不高。
可导航性表达了有关实现的一些承诺或约束。 因此,在早期阶段通常不指定可航行性。 在后面的阶段,您将明确显示必须支持的导航路径。 但很少有系统地完成,而且很少看到不可导航性(X 而不是箭头)。
结论:当你看到没有箭头和 X 时,你不能得出任何结论,除非你的团队已经定义了一些对这种情况更精确的约定。
关联可以通过多种方式实现。 典型的实现是在类 A 的对象中保留对类 B 的关联对象的一个或多个引用。根据定义,这确保了可导航性A ---> B
因为存在有效访问。 但是回来的路呢?
如果您什么都不做,对象 B 就无法轻松找到与其关联的对象 A。 如果引用不是私有的(这将使搜索变得不可能),唯一的方法是遍历所有 A 对象以找到其引用(可能但效率不高)。 所以你有一个不可导航的方式返回A x--> B
为了使其可双向导航,您将在 B 对象中保留对关联 A 对象的引用。 这使得可导航性是双向的: A <--> B
。
这里的主要挑战是您必须保持两个对象中的引用同步。
Person
A 保留了拥有的Car
C1、C2 和 C3 的列表,那么每个Car
C1、C2、C3 都会保留对其所有者 A 的引用。 设计问题:每个类都应该被很好地封装并使用另一个类的公共访问。 所以Owner.AddCar(Car)
和Car.SetOwner(Owner)
应该暴露在公共接口中。 但是,如果您在某处调用B.AddCar(C4)
,则AddCar()
方法将要调用C4.SetOwner(B)
以确保一致性。 但是SetOwner()
方法也可以从外部调用,因此也希望通过调用B.AddCar(C4)
来保持同步,然后......你会以两个方法的堆栈溢出结束,这些方法永远相互调用以维护同步。
Fowler 的代码通过通过friendCars()
授予Car
对Person
的汽车列表的特权访问来解决这个问题。 这解决了问题,但需要Car
了解Person
的内部结构并创建不必要的耦合。
我不是 Python 专家,但据我所知,您的代码是不同的:
cars_and_lnos
,其中包含所有汽车及其所有者的元组。__owned_by
仅在汽车__owned_by
更新,之后不会更新。 因此,在基于*arg
的添加过程中构建汽车时,它可能会起作用,但在将现有汽车添加到所有者时会失败。get_owner_v1()
失败,因为它基于__owned_by
。 但是get_owner_v2()
运行良好,因为它在cars_and_lnos
上迭代(非常低效)。简而言之,v1 失败是因为您的代码产生了不一致的对象。
话虽如此,您的方法比 Fowler 的方法更有前途:
cars_and_lnos
移到一个单独的类CarRegistration
,并根据精心设计的 api 重写Car
和Person
以与CarRegistration
交互以更新所有权。CarRegistration
,用 2 个字典替换cars_and_lnos
以优化搜索。Q2:是的。 没有箭头表示未指定。 这意味着可航行。 聚丙烯。 UML 2.5 的第 18 条:
箭头符号用于表示关联端可导航性。 根据定义,所有类拥有的关联端都是可导航的。 按照惯例,元模型中所有关联拥有的端都是不可导航的。
两端均未用可导航箭头标记的关联意味着该关联可在两个方向上导航。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.