Състав, агрегиране и асоцииране в Java

1. Въведение

Обектите имат взаимовръзки помежду си, както в реалния живот, така и в програмирането. Понякога е трудно да се разберат или приложат тези взаимоотношения.

В този урок ще се съсредоточим върху възприемането на Java на три понякога лесно смесени типа взаимоотношения: композиция, агрегиране и асоцииране.

2. Състав

Композицията е тип „връзка“. Това означава, че един от обектите е логически по-голяма структура, която съдържа другия обект. С други думи, това е част или член на другия обект.

Като алтернатива често го наричаме връзка „има-а“ (за разлика от връзката „има-а“, която е наследяване).

Например, една стая принадлежи на сграда, или с други думи сградата има стая. Така че по същество дали ще го наречем „принадлежи“ или „има-а“ е само въпрос на гледна точка.

Композицията е силен вид връзка „има-има“, тъй като съдържащият обект я притежава. Следователно жизненият цикъл на обектите е обвързан. Това означава, че ако унищожим обекта собственик, членовете му също ще бъдат унищожени с него. Например помещението е унищожено със сградата в предишния ни пример.

Имайте предвид, че това не означава, че съдържащият обект не може да съществува без нито една от частите му. Например, можем да съборим всички стени в сградата и следователно да разрушим стаите. Но сградата все пак ще съществува.

По отношение на мощността, съдържащият обект може да има толкова части, колкото искаме. Въпреки това, всички части трябва да има точно един контейнер .

2.1. UML

В UML посочваме състава със следния символ:

Имайте предвид, че диамантът е в съдържащия обект и е основата на линията, а не стрелка. За по-голяма яснота често рисуваме и стрелката:

И така, тогава можем да използваме тази UML конструкция за нашия пример за Building-Room:

2.2. Програмен код

В Java можем да моделираме това с нестатичен вътрешен клас:

class Building { class Room {} }

Като алтернатива можем да декларираме този клас и в тялото на метода. Няма значение дали е именован клас, анонимен клас или ламбда:

class Building { Room createAnonymousRoom() { return new Room() { @Override void doInRoom() {} }; } Room createInlineRoom() { class InlineRoom implements Room { @Override void doInRoom() {} } return new InlineRoom(); } Room createLambdaRoom() { return () -> {}; } interface Room { void doInRoom(); } }

Обърнете внимание, че е от съществено значение вътрешният ни клас да е нестатичен, тъй като свързва всички свои екземпляри със съдържащия клас.

Обикновено съдържащият обект иска достъп до своите членове. Затова трябва да съхраняваме техните референции:

class Building { List rooms; class Room {} }

Имайте предвид, че всички обекти от вътрешния клас съхраняват неявна препратка към съдържащия ги обект. В резултат на това не е нужно да го съхраняваме ръчно, за да го осъществим:

class Building { String address; class Room { String getBuildingAddress() { return Building.this.address; } } }

3. Агрегация

Агрегирането също е връзка „има-има“. Това, което го отличава от композицията, е, че не включва притежание. В резултат на това жизненият цикъл на обектите не е обвързан: всеки един от тях може да съществува независимо един от друг.

Например автомобил и колелата му. Можем да свалим колелата и те пак ще съществуват. Можем да монтираме други (вече съществуващи) колела или да ги инсталираме на друга кола и всичко ще работи добре.

Разбира се, автомобил без колела или отделено колело няма да бъде толкова полезен, колкото автомобил с включени колела. Но затова тази връзка съществува на първо място: да се съберат частите в по-голяма конструкция, която е способна на повече неща от своите части .

Тъй като агрегирането не включва притежание, членът не трябва да бъде обвързан само с един контейнер . Например, триъгълник е направен от сегменти. Но триъгълниците могат да споделят сегменти като свои страни.

3.1. UML

Агрегирането е много подобно на състава. Единствената логична разлика е, че агрегирането е по-слаба връзка.

Следователно UML представленията също са много сходни. Единствената разлика е, че диамантът е празен:

За автомобили и колела тогава бихме направили:

3.2. Програмен код

В Java можем да моделираме агрегирането с обикновена стара препратка:

class Wheel {} class Car { List wheels; }

Членът може да бъде от всякакъв тип клас, с изключение на нестатичен вътрешен клас.

В кодовия фрагмент над двата класа има отделен изходен файл. Можем обаче да използваме и статичен вътрешен клас:

class Car { List wheels; static class Wheel {} }

Имайте предвид, че Java ще създаде неявна препратка само в нестатични вътрешни класове. Поради това трябва да поддържаме връзката ръчно, където ни е необходимо:

class Wheel { Car car; } class Car { List wheels; }

4. Асоциация

Асоциацията е най-слабата връзка между тримата. Това не е връзка „има-има“ , нито един от обектите не е част или член на друг.

Асоциацията означава само, че обектите се „познават”. Например майка и нейното дете.

4.1. UML

В UML можем да маркираме асоциация със стрелка:

Ако асоциацията е двупосочна, можем да използваме две стрелки, стрелка със стрелка в двата края или линия без никакви върхове на стрелки:

We can represent a mother and her child in UML, then:

4.2. Source Code

In Java, we can model association the same way as aggregation:

class Child {} class Mother { List children; }

But wait, how can we tell if a reference means aggregation or association?

Well, we can't. The difference is only logical: whether one of the objects is part of the other or not.

Also, we have to maintain the references manually on both ends as we did with aggregation:

class Child { Mother mother; } class Mother { List children; }

5. UML Sidenote

For the sake of clarity, sometimes we want to define the cardinality of a relationship on a UML diagram. We can do this by writing it to the ends of the arrow:

Note, that it doesn't make sense to write zero as cardinality, because it means there's no relationship. The only exception is when we want to use a range to indicate an optional relationship:

Also note, that since in composition there's precisely one owner we don't indicate it on the diagrams.

6. A Complex Example

Let's see a (little) more complex example!

We'll model a university, which has its departments. Professors work in each department, who also has friends among each other.

Will the departments exist after we close the university? Of course not, therefore it's a composition.

But the professors will still exist (hopefully). We have to decide which is more logical: if we consider professors as parts of the departments or not. Alternatively: are they members of the departments or not? Yes, they are. Hence it's an aggregation. On top of that, a professor can work in multiple departments.

Връзката между професорите е асоциация, защото няма смисъл да се казва, че професорът е част от друг.

В резултат на това можем да моделираме този пример със следната UML диаграма:

И кодът на Java изглежда така:

class University { List department; } class Department { List professors; } class Professor { List department; List friends; }

Имайте предвид, че ако разчитаме на термините „има-a“, „принадлежи-на“, „член-на“, „част от“ и т.н., можем по-лесно да идентифицираме връзките между нашите обекти.

7. Заключение

В тази статия видяхме свойствата и представянето на състава, агрегирането и асоциирането. Видяхме също как да моделираме тези взаимоотношения в UML и Java.

Както обикновено, примерите са достъпни в GitHub.