In Martin Fowler's, UML Distilled, in the "Bidirectional Association" section, he says:
Implementing a bidirectional association in a programming language is often a little tricky because you have to be sure that both properties are kept synchronized. Using C#, I use code along these lines to implement a bidirectional association:
Code from the book
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;
}
....
Question 1:
I tried implementing this in python (get_cars() in Person && get_owner_v2() in Car), I want to know if my code can be used to describe a 'bidirectional association' or not, and if not, how should it be modified to do so?
Note: the first version (inspecting the caller's class/object) was working fine until I started creating car object independently and assigning them to owner on two steps (last 4 print statement proves that). The second version uses the licence numeber lno to figure out the owner. Not sure if I did it correctly but it does the work, based on my understanding.
My Implementation:
#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())
Question 2:
In this section he says the that these two notations are likely the same:
Is the following notation (without any arrows specified) can also be considered bidirectional relationship?
EDIT:
I understand that a logical refinement to the class diagram would be * to * (many to many), but I'm only concerned about how correct my implementation to the description/design is and how can it be improved, and where the no-arrows association line fit in the picture.


