Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Моделирование предметной области Java: Объектно-ориентированный дизайн

Работая в Java, мы постоянно будем сталкиваться с необходимостью создавать новые классы. Многие из этих классов не существуют сами по себе, они связаны друг с другом, что порождает множество вопросов о том как их правильно проектировать. В этом уроке мы рассмотрим базовые принципы проектирования классов.

Любое приложение строится вокруг какой-то предметной области. Например, Хекслет занимается обучением, поэтому его предметная область включает в себя такие понятия как студент, курс, урок, наставник, автор курса и многое другое. Эти понятия существуют сами по себе независимо от того, что, как и на чем мы программируем. Наша задача как программистов состоит в том, чтобы перенести эти понятия на код и реализовать необходимые сценарии поведения. Этот процесс называется моделированием предметной области.

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

// Course
import java.util.ArrayList;
import java.util.List;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Course {
    private String name;
    private List<Lesson> lessons;

    public Course(String name) {
        this.name = name;
        lessons = new ArrayList<>();
    }
}

// Lesson
import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
public class Lesson {
    private String name;
    private Course course;

    public Lesson(String name, Course course) {
        this.name = name;
        this.course = course;
    }
}

Самое интересное в этих классах то, что они связаны друг с другом. У курса может быть много уроков, в свою очередь у урока может быть только один курс, которому он принадлежит. Эта связь называется один ко многим (one to many, o2m) и она очень распространена в реальной жизни.

Реализация этой связи включает три элемента:

  • Поле с коллекцией на одном конце, в нашем примере это lessons в курсе.
  • Поле с родительским объектом на другом конце, в нашем примере это course в уроке.
  • Логика добавления и удаления урока из курса

Посмотрим на сценарий добавления уроков в курс.

var course = new Course("Java ООП");
course.getLessons().add(new Lesson("Builder", course));
course.getLessons().add(new Lesson("Singleton", course));

Обратите внимание на то, что связь обоюдная. С одной стороны урок добавляется в курс, с другой, курс добавляется в урок. Только в этом случае логика не будет нарушена.

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

var course = new Course("Java ООП");

// Добавление
var lesson = new Lesson("Builder", course);
course.getLessons().add(lesson);

// Удаление
course.getLessons().remove(lesson);
lesson.setCourse(null);

Код выше построен таким образом, что логика добавления и удаления урока в курсе должна контролироваться программистом. Нужно не забывать при добавлении урока в курс, добавлять курс в урок и наоборот при удалении. Кроме того, сам процесс изменения коллекции lessons происходит напрямую, без уведомления курса, что может стать проблемой при появлении дополнительной логики. Например, логика приложения может требовать наличия минимум одного урока в курсе. Выполнение этого правила невозможно обеспечить кодом выше.

Описанная проблема, называется нарушением инкапсуляции. То есть изменение частей объекта происходит снаружи объекта, за счет получения прямой ссылки на внутренности объекта (getLessons). Правильный же подход основан на том, что логика манипуляции коллекцией находится внутри объекта, содержащего эту коллекцию. В нашем случае внутри курса.

@Getter
@Setter
public class Course {
    private String name;
    private List<Lesson> lessons;

    public Course(String name) {
        this.name = name;
        lessons = new ArrayList<>();
    }

    public void addLesson(Lesson lesson) {
        lesson.setCourse(this);
        lessons.add(lesson);
    }

    public void removeLesson(Lesson lesson) {
        lesson.setCourse(null);
        lessons.remove(lesson);
    }
}

var course = new Course("Java ООП");
var lesson = new Lesson("Builder");
course.addLesson(lesson);
course.addLesson(new Lesson("Singleton"));
course.removeLesson(lesson);

Задачка. Как бы вы реализовали указание порядка уроков в курсе?

UML

Для структуры классов приложения их связей был придуман способ визуального представления, называемый диаграммой классов. Диаграмма классов это один из элементов UML (Unified Modeling Language), визуального языка для описания программных систем.

Эти диаграммы часто используются в книгах и документации, поэтому полезно уметь их читать. Кроме того, они иногда используются в компаниях, в качестве документации к проекту.

  +-------------------+
  |      Course       |
  +-------------------+
  | - name: String    |
  | - lessons: List   |
  +-------------------+
  | + addLesson()     |
  | + getName()       |
  | + setName()       |
  | + getLessons()    |
  +-------------------+
          | 1
          |
          |
          v 0..*
  +-------------------+
  |      Lesson       |
  +-------------------+
  | - name: String    |
  | - course: Course  |
  +-------------------+
  | + getName()       |
  | + setName()       |
  | + getCourse()     |
  | + setCourse()     |
  +-------------------+

Расшифруем обозначения:

  • Блок класса разбит на три секции: название, поля, методы.
  • Минус означает приватность, плюс - публичность.
  • 1 ---> 0..* показывает связь один ко многим. 1 говорит о том, что с этого конца одно значение (у урока ровно один курс), 0..* говорит о том, что с этого конца может быть любое количество элементов (у курса может быть сколько угодно уроков). Иногда для простоты такую связь показывают как *.

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


Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Об обучении на Хекслете

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 6 300 ₽ в месяц
Разработка приложений на языке Java
10 месяцев
с нуля
Старт 23 мая

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»