在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()和&Person中是get_owner_v2(),在Car中),我想知道我的代码是否可用于描述“双向关联” ,如果没有,应该如何修改呢?

注意:第一个版本(检查调用方的类/对象)运行良好,直到我开始独立创建汽车对象并将其分两步分配给所有者(最后四个打印语句证明了这一点)。第二个版本使用许可证编号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","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","Chevrolet","Camaro")
car2 = Car("6271531","Ford","Mustang")
print(car2.get_owner_v1())

print("=====================================================")

owner3 = Person("Lyan")
owner3.add_car("656721",9000,2013,"Camry")
owner3.add_car("652901","Nissan","Sunny")
owner3.add_car("870251","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","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:

在本节中,他说这两种表示法可能是相同的:

enter image description here

enter image description here

以下符号(未指定任何箭头)是否也可以视为双向关系?

enter image description here

编辑:

我知道对类图的逻辑改进是*到*(很多),但是我只担心我的实现对描述/设计的正确程度以及如何改进它/在哪里进行?图片中没有箭头的关联线。

解决方法

问题2是already perfectly answered。因此,在关注问题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的引用。
  • li>
  • 如果将汽车C2的所有者更改为B,则最好更新所有者A的汽车列表(删除C2)和所有者B的汽车列表(添加C2)。
  • 如果B将拥有新车C4的车辆添加到其拥有的汽车列表中,则C4的所有者应相应地更新。

设计问题:每个类都应进行很好的封装,并使用其他类的公共访问权限。因此,Owner.AddCar(Car)Car.SetOwner(Owner)应该在公共界面中公开。但是,如果您要调用B.AddCar(C4)的某个地方,则AddCar()方法将要调用C4.SetOwner(B)以确保并发性。但是SetOwner()方法也可以从外部调用,因此woultd也希望通过调用B.AddCar(C4)来保持同步,然后...最终将导致两个方法的堆栈溢出,这些方法永远相互调用保持同步。

福勒的代码通过向Car授予通过Person访问friendCars()的汽车列表的特权,解决了这个问题。这样可以解决问题,但需要Car知道Person的内部知识并创建不必要的耦合。

您的代码有问题

我不是Python的专家,但据我了解,您的代码与众不同:

  • 您维护一个类变量cars_and_lnos,其中包含所有汽车及其所有者的元组。
  • 将汽车添加到拥有的汽车时,此人会维护此列表。
  • 汽车的__owned_by仅在汽车制造时更新,而不在之后更新。因此,当在基于*arg的添加过程中构造汽车时,它可能会起作用,但在将现有汽车添加到所有者中时,它会失败。
  • 因此,get_owner_v1()失败,因为它基于__owned_by。但是get_owner_v2()的工作效果很好,因为它在最新的cars_and_lnos上进行了迭代(效率很低)。

简而言之,v1失败是因为您的代码生成了不一致的对象。

话虽如此,您的方法比福勒的方法更有前途:

  • 步骤1:您可以将cars_and_lnos移到单独的类CarRegistration中,并重写CarPersonCarRegistration进行交互,以基于精心设计的api。
  • 第2步:重构当时有效但效率不高的CarRegistration,用2个字典替换cars_and_lnos,以进行优化搜索。
,

Q2:是的。没有箭头表示未指定。这意味着可以导航。 PP。 UML 2.5中的18:

  • 箭头符号用于表示关联端点的可导航性。根据定义,所有类拥有的关联端点都是可导航的。按照惯例,元模型中所有关联拥有的端点都是不可导航的。

  • 两端都没有用可导航性箭头标记的关联意味着该关联可以在两个方向上导航。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...