До сих пор мы говорили об однонаправленных ассоциациях. К другому распространенному типу ассоциаций относится двунаправленная ассоциация, например, показанная на рис. 3.4.
Двунаправленная ассоциация - это пара свойств, связанных в противоположных направлениях. Класс Саг (Автомобиль) имеет свойство
owner: Person[1], а класс Person (Личность) имеет свойство cars:Car[*]. (Обратите внимание, что я использовал множественную форму имени свойства cars, а это соглашение общепринятое, но ненормативное.)
Обратная связь между ними подразумевает, что если вы следуете обоим свойствам, то должны вернуться обратно к множеству, содержащему вашу исходную точку. Например, если я начинаю с конкретной модели MG Midget, нахожу ее владельца, а затем смотрю на множество принадлежащих ему машин, то оно должно включать модель Midget, с которой я начал.
В качестве альтернативы маркировки ассоциации по свойству многие люди, особенно если они имеют опыт моделирования данных, любят именовать ассоциации с помощью глаголов (рис. 3.5), чтобы отношение можно было использовать в предложении. Это вполне допустимо, и можно добавить к ассоциации стрелку, чтобы избежать неопределенности. Большинство разработчиков объектов предпочитают использовать имя свойства, так как оно больше соответствует функциональным назначениям и операциям.
Некоторые разработчики тем или иным способом именуют каждую ассоциацию. Я предпочитаю давать имя ассоциации, только если это улучшает понимание. Слишком часто встречаются такие имена, как «has» (имеет) или «is related to» (связан с).
На рис. 3.4 двунаправленная природа ассоциации подчеркивается стрелками на обоих концах ассоциации. На рис. 3.5 стрелок нет; в языке UML эта форма применяется либо для обозначения двунаправленной ассоциации, либо когда направление отношения не показывается. Я предпочитаю обозначать двунаправленную ассоциацию с помощью двойных стрелок.
Рис. 3.5. Использование глагола (own - владеть) в имени ассоциации
Реализация двунаправленной ассоциации в языке программирования часто представляет некоторую сложность, поскольку необходимо обеспечить синхронизацию обоих свойств. В С# для реализации двунаправленной ассоциации я делаю следующее:
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 ArrayListQ;
internal IList friendCars() {
//должен быть использован только Car.Owner
return _cars;
}
….
Главное - сделать так, чтобы одна сторона ассоциации (по возможности с единственным значением) управляла всем отношением. Для этого ведомый конец (Person) должен предоставить инкапсуляцию своих данных ведущему концу. Это приводит к добавлению в ведомый класс не очень удобного метода, которого здесь не должно было бы быть в действительности, если только язык не имеет более тонкого инструмента управления доступом. Я здесь употребил слово «friend» (друг) в имени как намек на C++, где метод установки ведущего класса действительно был бы дружественным. Как и большинство кода, работающего со свойствами, это стереотипный фрагмент, и поэтому многие разработчики предпочитают получать его посредством различных способов генерации кода.
В концептуальных моделях навигация не очень важна, поэтому в таких случаях я не показываю каких-либо навигационных стрелок.