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

в 18:58, , рубрики: java, UML, UML Design, метки: ,
Введение

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

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

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

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

1. Обобщение

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

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

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

Отношения классов — от UML к коду
Рис. 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.
Отношения классов — от UML к коду
Рис. 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.
Отношения классов — от UML к коду
Рис. 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(отдел) — наше предприятие структурировано по отделам. В каждом отделе может работать один или более человек. Можно сказать, что отдел включает в себя одного или более сотрудников и таким образом их агрегирует. На предприятии могут быть сотрудники, которые не принадлежат ни одному отделу, например, директор предприятия.
Отношения классов — от UML к коду
Рис. 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».
Отношения классов — от UML к коду
Рис. 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».
Отношения классов — от UML к коду
Рис. 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».

Отношения классов — от UML к коду
Рис. 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(реализация).
В результате моделирования получили следующую диаграмму:

Отношения классов — от UML к коду
Рис. 8 — Диаграмма классов

Литература

1) Г. Буч, Д. Рамбо, А. Джекобсон. Язык UML Руководство пользователя.

2) А.В. Леоненков. Самоучитель UML

3) Эккель Б. Философия Java. Библиотека программиста. — СПб: Питер, 2001. — 880 с.

4) Орлов С. Технологии разработки программного обеспечения: Учебник. — СПб: Питер, 2002. — 464 с.

5) Мухортов В.В., Рылов В.Ю.Объектно-ориентированноепрограммирование, анализ и дизайн. Методическоепособие. — Новосибирск, 2002.

6) Anand Ganesan. Modeling Class Relationships in UML

Автор: zesetup

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js