Введение
Диаграмма классов UML позволяет обозначать отношения между классами и их экземплярами. Для чего они нужны? Они нужны, например, для моделирования прикладной области. Но как отношения отражаются в программном коде? Данное небольшое исследование пытается ответить на этот вопрос — показать эти отношения в коде.
Сначала попробуем прояснить, как относятся друг к другу отношения между классами в UML. Используя различные источники удалось построить следующую структурную схему, демонстрирующую разновидности отношений:
Рис. 1 — Отношения между классами
Ассоциации имеют навигацию: двунаправленную или однонаправленную, указывающую на направление связи. То есть у каждого вида ассоциации еще есть два подвида, которое на рисунке не показаны.
1. Обобщение
Итак, наша цель — построить UML-диаграмму классов (Class Model), а затем отразить ее в объектно-ориентированном коде.
В качестве прикладной области возьмем отдел кадров некого предприятия и начнем строить его модель. Для примеров будем использовать язык Java.
Отношение обобщения — это наследование. Это отношение хорошо рассматривается в каждом учебнике какому-либо ООП языку. В языке Java имеет явную реализацию через расширение(extends) одного класса другим.
Рис. 2 — Отношение обобщения
Класс «Man»(человек) — более абстрактный, а «Employee»(сотрудник) более специализированный. Класс «Employee» наследует свойства и методы «Man».
Попробуем написать код для этой диаграммы:
public static class Man{
protected String name;
protected String surname;
public void setName(String newName){
name = newName;
}
public String getName(){
return name;
}
public void setSurname(String newSurname){
name = newSurname;
}
public String getSurname(){
return surname;
}
}
// наследуем класс Man
public static class Employee extends Man{
private String position;
// создаем и конструктор
public Employee(String n, String s, String p){
name = n;
surname = s;
position = p;
}
public void setPosition(String newProfession){
position = newProfession;
}
public String getPosition(){
return position;
}
}
2. Ассоциация
Ассоциация показывает отношения между объектами-экземплярами класса.
2.1 Бинарная
В модель добавили класс «IdCard», представляющий идентификационную карточку(пропуск) сотрудника. Каждому сотруднику может соответствовать только одна идентификационная карточка, мощность связи 1 к 1.
Рис. 3 — Бинарная ассоциация
Классы:
public static class Employee extends Man{
private String position;
private IdCard iCard;
public Employee(String n, String s, String p){
name = n;
surname = s;
position = p;
}
public void setPosition(String newPosition){
position = newPosition;
}
public String getPosition(){
return position;
}
public void setIdCard(IdCard c){
iCard = c;
}
public IdCard getIdCard(){
return iCard;
}
}
public static class IdCard{
private Date dateExpire;
private int number;
public IdCard(int n){
number = n;
}
public void setNumber(int newNumber){
number = newNumber;
}
public int getNumber(){
return number;
}
public void setDateExpire(Date newDateExpire){
dateExpire = newDateExpire;
}
public Date getDateExpire(){
return dateExpire;
}
}
В теле программы создаем объекты и связываем их:
IdCard card = new IdCard(123);
card.setDateExpire(new SimpleDateFormat("yyyy-MM-dd").parse("2015-12-31"));
sysEngineer.setIdCard(card);
System.out.println(sysEngineer.getName() +" работает в должности "+ sysEngineer.getPosition());
System.out.println("Удостовирение действует до " + new SimpleDateFormat("yyyy-MM-dd").format(sysEngineer.getIdCard().getDateExpire()) );
Класс Employee имеет поле card, у которого тип IdCard, так же класс имеет методы для присваивания значения(setIdCard) этому полю и для
получения значения(getIdCard). Из экземпляра объекта Employee мы можем узнать о связанном с ним объектом типа IdCard, значит
навигация (стрелочка на линии) направлена от Employee к IdCard.
2.2 N-арная ассоциация
Представим, что в организации положено закреплять за работниками помещения. Добавляем новый класс Room.
Каждому объекты работник(Employee) может соответствовать несколько рабочих помещений. Мощность связи один-ко-многим.
Навигация от Employee к Room.
Рис. 4 — N-арная ассоциация
Теперь попробуем отразить это в коде. Новый класс Room:
public static class Room{
private int number;
public Room(int n){
number = n;
}
public void setNumber(int newNumber){
number = newNumber;
}
public int getNumber(){
return number;
}
}
Добавим в класс Employee поле и методы для работы с Room:
...
private Set room = new HashSet();
...
public void setRoom(Room newRoom){
room.add(newRoom);
}
public Set getRoom(){
return room;
}
public void deleteRoom(Room r){
room.remove(r);
}
...
Пример использвания:
public static void main(String[] args){
Employee sysEngineer = new Employee("John", "Connor", "Manager");
IdCard card = new IdCard(123);
card.setDateExpire(new SimpleDateFormat("yyyy-MM-dd").parse("2015-12-31"));
sysEngineer.setIdCard(card);
Room room101 = new Room(101);
Room room321 = new Room(321);
sysEngineer.setRoom(room101);
sysEngineer.setRoom(room321);
System.out.println(sysEngineer.getName() +" работает в должности "+ sysEngineer.getPosition());
System.out.println("Удостовирение действует до " + sysEngineer.getIdCard().dateExpire);
System.out.println("Может находиться в помещеньях:");
Iterator iter = sysEngineer.getRoom().iterator();
while(iter.hasNext()){
System.out.println( ((Room) iter.next()).getNumber());
}
}
2.3 Агрегация
Введем в модель класс Department(отдел) — наше предприятие структурировано по отделам. В каждом отделе может работать один или более человек. Можно сказать, что отдел включает в себя одного или более сотрудников и таким образом их агрегирует. На предприятии могут быть сотрудники, которые не принадлежат ни одному отделу, например, директор предприятия.
Рис. 5 — Агрегация
Класс Department:
public static class Department{
private String name;
private Set employees = new HashSet();
public Department(String n){
name = n;
}
public void setName(String newName){
name = newName;
}
public String getName(){
return name;
}
public void addEmployee(Employee newEmployee){
employees.add(newEmployee);
// связываем сотрудника с этим отделом
newEmployee.setDepartment(this);
}
public Set getEmployees(){
return employees;
}
public void removeEmployee(Employee e){
employees.remove(e);
}
}
Итак, наш класс, помимо конструктора и метода изменения имени отдела, имеет методы для занесения в отдел нового сотрудника, для удаления сотрудника и для получения всех сотрудников входящих в данный отдел. Навигация на диаграмме не показана, значит она является двунаправленной: от объекта типа «Department» можно узнать о сотруднике и от объекта типа «Employee» можно узнать к какому отделу он относится.
Так как нам нужно легко узнавать какому отделу относится какой-либо сотрудник, то добавим в класс Employee поле и методы для назначения и получения отдела.
...
private Department department;
...
public void setDepartment(Department d){
department = d;
}
public Department getDepartment(){
return department;
}
Использование:
Department programmersDepartment = new Department("Программисты");
programmersDepartment.addEmployee(sysEngineer);
System.out.println("Относится к отделу "+sysEngineer.getDepartment().name);
2.3.1 Композиция
Предположим, что одним из требований к нашей системе является требование о том, чтоб хранить данные о прежней занимаемой должности на предприятии.
Введем новый класс «pastPosition». В него, помимо свойства «имя»(name), введем и свойство «department», которое свяжет его с классом «Department».
Данные о прошлых занимаемых должностях являются частью данных о сотруднике, таким образом между ними связь целое-часть и в то же время, данные о прошлых должностях не могут существовать без объекта типа «Employee». Уничтожение объекта «Employee» должно привести к уничтожению объектов «pastPosition».
Рис. 6 — Композиция
Класс «PastPosition»:
private static class PastPosition{
private String name;
private Department department;
public PastPosition(String position, Department dep){
name = position;
department = dep;
}
public void setName(String newName){
name = newName;
}
public String getName(){
return name;
}
public void setDepartment(Department d){
department = d;
}
public Department getDepartment(){
return department;
}
}
В класс Employee добавим свойства и методы для работы с данными о прошлой должности:
...
private Set pastPosition = new HashSet();
...
public void setPastPosition(PastPosition p){
pastPosition.add(p);
}
public Set getPastPosition(){
return pastPosition;
}
public void deletePastPosition(PastPosition p){
pastPosition.remove(p);
}
...
Применение:
// изменяем должность
sysEngineer.setPosition("Сторож");
// смотрим ранее занимаемые должности:
System.out.println("В прошлом работал как:");
Iterator iter = sysEngineer.getPastPosition().iterator();
while(iter.hasNext()){
System.out.println( ((PastPosition) iter.next()).getName());
}
3. Зависимость
Для организации диалога с пользователем введем в систему класс «Menu». Встроим один метод «showEmployees», который показывает список сотрудников и их должности. Параметром для метода является массив объектов «Employee». Таким образом, изменения внесенные в класс «Employee» могут потребовать и изменения класса «Menu».
Рис. 7 — Зависимость
Заметим, что класс «Menu» не относится к прикладной области, а представляет собой «системный» класс воображаемого приложения.
Класс «Menu»:
public static class Menu{
private static int i=0;
public static void showEmployees(Employee[] employees){
System.out.println("Список сотрудников:");
for (i=0; i<employees.length; i++){
if(employees[i] instanceof Employee){
System.out.println(employees[i].getName() +" - " + employees[i].getPosition());
}
}
}
}
Использование:
// добавим еще одного сотрудника
Employee director = new Employee("Федор", "Дубов", "Директор");
Menu menu = new Menu();
Employee employees[] = new Employee[10];
employees[0]= sysEngineer;
employees[1] = director;
Menu.showEmployees(employees);
4. Реализация
Реализация, как и наследование имеет явное выражение в языке Java: объявление интерфейса и возможность его реализации каким-либо классом.
Для демонстрации отношения «реализация» создадим интерфейс «Unit». Если представить, что организация может делиться не только на отделы, а например, на цеха, филиалы и т.д. Интерфейс «Unit» представляет собой самую абстрактную единицу деления. В каждой единице деления работает какое-то количество сотрудников, поэтому метод для получения количества работающих людей будет актуален для каждого класса реализующего интерфейс «Unit».
Рис. 8 — Реализация
Интерфейс «Unit»:
public interface Unit{
int getPersonCount();
}
Реализация в классе «Department»:
public static class Department implements Unit{
...
public int getPersonCount(){
return getEmployees().size();
}
Применение:
System.out.println("В отделе "+sysEngineer.getDepartment().name+" работает "
+sysEngineer.getDepartment().getPersonCount()+" человек.");
Как видим, реализация метода «getPersonCount» не совсем актуальна для класса «Department», так как он имеет метод «getEmployees», который возвращает
коллекцию объектов «Employee».
Код полностью: umljava.googlecode.com/files/UmlRelations.java
Выводы
Язык моделирования UML имеет набор отношений для построения модели классов, но даже такой развитой ООП язык, как Java имеет только две явные конструкции для отражения связей: extends(расширение) и interface/implements(реализация).
В результате моделирования получили следующую диаграмму:
Рис. 8 — Диаграмма классов
Литература
1) Г. Буч, Д. Рамбо, А. Джекобсон. Язык UML Руководство пользователя.
2) А.В. Леоненков. Самоучитель UML
3) Эккель Б. Философия Java. Библиотека программиста. — СПб: Питер, 2001. — 880 с.
4) Орлов С. Технологии разработки программного обеспечения: Учебник. — СПб: Питер, 2002. — 464 с.
5) Мухортов В.В., Рылов В.Ю.Объектно-ориентированноепрограммирование, анализ и дизайн. Методическоепособие. — Новосибирск, 2002.
6) Anand Ganesan. Modeling Class Relationships in UML
Автор: zesetup