Отношения классов — от UML к коду

Введение

Диаграмма классов UML позволяет обозначать отношения между классами и их экземплярами. Для чего они нужны? Они нужны, например, для моделирования прикладной области. Но как отношения отражаются в программном коде? Данное небольшое исследование пытается ответить на этот вопрос — показать эти отношения в коде.

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


Рис. 1 — Отношения между классами

Ассоциации имеют навигацию: двунаправленную или однонаправленную, указывающую на направление связи. То есть у каждого вида ассоциации еще есть два подвида, которое на рисунке не показаны.

1. Обобщение

Итак, наша цель — построить UML-диаграмму классов (Class Model), а затем отразить ее в объектно-ориентированном коде.

В качестве прикладной области возьмем отдел кадров некого предприятия и начнем строить его модель. Для примеров будем использовать язык Java.

Отношение обобщения — это наследование. Это отношение хорошо рассматривается в каждом учебнике какому-либо ООП языку. В языке Java имеет явную реализацию через расширение(extends) одного класса другим.


Рис. 2 — Отношение обобщения

Класс «Man»(человек) — более абстрактный, а «Employee»(сотрудник) более специализированный. Класс «Employee» наследует свойства и методы «Man».

Попробуем написать код для этой диаграммы:
public 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 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 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 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  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().getDateExpire());
    System.out.println("Может находиться в помещеньях:");
    Iterator iter = sysEngineer.getRoom().iterator();
    while(iter.hasNext()){
         System.out.println( ((Room) iter.next()).getNumber());
	}
}

2.3 Агрегация

Введем в модель класс Department(отдел) — наше предприятие структурировано по отделам. В каждом отделе может работать один или более человек. Можно сказать, что отдел включает в себя одного или более сотрудников и таким образом их агрегирует. На предприятии могут быть сотрудники, которые не принадлежат ни одному отделу, например, директор предприятия.

Рис. 5 — Агрегация

Класс Department:
public 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().getName());


2.3.1 Композиция

Предположим, что одним из требований к нашей системе является требование о том, чтоб хранить данные о прежней занимаемой должности на предприятии.
Введем новый класс «pastPosition». В него, помимо свойства «имя»(name), введем и свойство «department», которое свяжет его с классом «Department».

Данные о прошлых занимаемых должностях являются частью данных о сотруднике, таким образом между ними связь целое-часть и в то же время, данные о прошлых должностях не могут существовать без объекта типа «Employee». Уничтожение объекта «Employee» должно привести к уничтожению объектов «pastPosition».

Рис. 6 — Композиция

Класс «PastPosition»:
private 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  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 class Department implements Unit{
    ...
    public int getPersonCount(){
        return getEmployees().size();
    }


Применение:
System.out.println("В отделе "+sysEngineer.getDepartment().getName()+" работает "
+sysEngineer.getDepartment().getPersonCount()+" человек.");


Как видим, реализация метода «getPersonCount» не совсем актуальна для класса «Department», так как он имеет метод «getEmployees», который возвращает
коллекцию объектов «Employee».

Код полностью: http://code.google.com/p/umljava/downloads/list

Выводы

Язык моделирования 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
Поделиться публикацией

Похожие публикации

Комментарии 21
    0
    Отлично, спасибо. А что на счет отношения Bind (связывание шаблонного, generic в Java, класса с формальным параметром типа)? И далее комбинаций: ассоциация + связывание, зависимость + связывание и т.д.
    Например вот для такого кода:
    class Foo {
        public Bar<Baz> field;
    }
    
    class Bar<T> { /* ... */ }
    
    class Baz { /* ... */ }
    
    как должна выглядеть диаграмма?
      0
      Обощения и шаблоны не показывают на диаграммах. В лючшем случае так:
      image

      Взято отсюда plantuml.sourceforge.net/classes.html
        0
        Извините за ошибки, глаза слипаются.
        0
        Хоть какая польза от учебы в университете — по названию топика сразу все вспомнил.
        Bind как и еще 4-5 разных связей можно отображать непосредственно подписью над отношением (но как правило не нужно :) ). По крайней мере VP это делает за Вас, все же зависит от конкретной нотации.
        +1
        Гладко было на бумаге, да забыли про овраги…
          0
          Сейчас изучаю паттерны проектирования. Соответственно там рассматривается ООП. Книга полна таких вот диаграмм. Так что топик прямо в тему!
          Но возник такой вопрос: в ООП встречается такое понятие как «интерфейс». Википедия дала слишком абстрактный ответ на вопрос «что это». Глубоко пока не гуглил. Может кто-то тут на пальцах объяснит что подразумевается под понятием «интерфейс» в ООП. А лучше приведет пример! Буду очень признателен!
            0
            Но возник такой вопрос: в ООП встречается такое понятие как «интерфейс». Википедия дала слишком абстрактный ответ на вопрос «что это». Глубоко пока не гуглил. Может кто-то тут на пальцах объяснит что подразумевается под понятием «интерфейс» в ООП. А лучше приведет пример! Буду очень признателен!


            Интерфейс — это абсолютно абстрактный класс, который не содержит реализаций методов, а только их объявления.
            Классы могут реализовывать один или более интерфейсов.

              0
              Это плохое определение, показывающее технические особенности реализации, а не суть объекта.

              Концептуально, интерфейс — это контракт.

              А технически, да, чисто абстрактный класс. Впрочем, даже в яве это не совсем так — бывают интерфейсы вообще без объявлений методов (напр. Serializable), но контракт таки объявляющий.
                0
                Да, но только с оговоркой, что конкретно в java интерфейс не позволяет описать контрак. Контрак подразумевает некую семантику, что нельзя задать в чисто абстрактном классе.
                  0
                  Интерфейс Serializable как раз и подразумевает некую семантику, отличную от технически чисто абстрактного класса. А так, да, средства описания контрактов с помощью явских интефейсов весьма скудны.
              0
              Интерфейс — это артефакт, описывающий способ взаимодействия двух частей системы. Интерфейсы в java позволяют только описывать сигнатуры методов (то есть по сути является полностью абстрактым классом, не более того), таким образом недостающую часть конракта нужно описывать в javadoc интерфеса.
              0
              напомнило диплом и бессонные ночи в Enterprise Architect)
                0
                Почему у вас классы имеют static?
                  0
                  вы правы, это ни к чему. убираю…
                  0
                    0
                    Получается, что вы называете имена отношений, только в одностороннем направлении.
                    То есть, про агригацию:
                    если смотреть на отношение от Департамента к Работнику, то связь — агрегация
                    если смотреть на отношения от Работника к Департаментуто, связь бинарная ассоциация

                    Верно?

                    И почему агрегация, а не ассоциация в отношениях между Департаментом и Работником?
                    В чем разница между:
                    Департамент агрегирует Работников (агрегация)
                    и
                    Каждый Департамент содержит ноль или несоклько работников (N-рная ассоциация)?

                    Спасибо.
                      0
                      Думаю, что ответ слегка позноват :)
                      Стрелочки в диаграмме зависят от точки зрения автора в момент составления. По ним можно проследить порядок возникновения сущностей и связей.
                      Так же этим можно пользоваться чтобы специально расставить акценты и сконцентрировать внимание зрителей вокруг тех или иных объектов, которые с точки зрения автора являются ключевыми.
                      Но да, во многих ситуациях техническая реализация не будет отличаться.
                      0
                      В агрегации описано:
                      … отдел включает в себя одного или более сотрудников и таким образом их агрегирует. На предприятии могут быть сотрудники, которые не принадлежат ни одному отделу, например, директор предприятия.

                      В то время как диаграмма показывает, что отдел включает в себя 0 или более сотрудников, в то время как каждый сотрудник обязательно должен относиться к одному из отделов.
                      Вопрос следующий. Это ошибка в диаграмме (или описании) или я не правильно понимаю вопрос?
                      Спасибо.
                        0

                        Тут явная архитектурная ошибка: PastPosition — это класс, и Employee содержит список этих pastPositions и тут же рядом этот же PastPosition (правильно просто Position) раскрыт в переменные-поля: position (правильно currentPosition) и department.


                        На мой взгляд логично содержать единый список positions, где positions[0] — это текущая должность, а getCurrentPosition() возвращает из списка понятно что. Будет красивее и яснее, more clear.

                          0
                          Соглашусь частично. Действительно, должен быть класс Position, у которого есть свойства name и department.
                          А у сотрудника Employee должны быть свойства currentPosition и pastPositions[]. Сливать текущую и прошлые позиции в один массив я бы не стал, это все же разные сущности. Хранить текущую позицию в positions[0] — это совсем не clear.
                          0
                          Статья неплохая, освежила память, хоть и смахивает на реферат. Неплохой реферат )

                          Есть один тонкий момент, который мог бы сделать из просто неплохой статьи по-настоящему ценную. В агрегации приводится метод addEmployee класса Department. И в этом методе производятся сразу два действия: 1) добавление работника в массив работников отдела и 2) указание отдела в объекте работника.
                          Если следовать данной логике, то как минимум в методе removeEmployee надо не забыть почистить отдел у сотрудника, а не просто удалить сотрудника из массива.
                          А как максимум — нужно ли нагружать класс Department дополнительной ответственностью? Не просто добавлять сотрудника в свой массив, но и сообщать сотруднику о себе?
                          А может наоборот, в методе setDepartment класса Employee надо вызывать department.addEmployee(this)?
                          А может вообще сделать некий промежуточный класс «отдел кадров», который будет выполнять оба действия: сообщать сотруднику его отдел и сообщать отделу о новом сотруднике?

                          На мой взгляд, вывод в статье должен быть в том, что переход от UML к коду — это может быть и не так сложно, но вот прийти к самому UML — это дело другого уровня. Это задача проектирования предметной области. На эту тему можно начать с чтения Эрика Эванса.

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                          Самое читаемое