На главную

Компоновщик

Напишем todo-лист на стероидах — используем паттерн "Дерево" чтобы написать модель данных будущего приложения

Что может быть популярней чем стартый-добрый todo-лист? Наверно, это самый популярный тип приложения, который создают чтобы потрогать какую-то новую технологию. Суть: можно создать задачку, переименовать её, пометить сделаной, пометить несделаной, удалить.

пример туду-листа
Знакомый нам список задачек, он же todo-лист

Но, конечно в реальной жизни такого функционала пользователям недостаточно. Давайте сделаем приложение интересней, добавим пару фич.

Усложним функционал

Прежде всего, напрашивается возможность объединения задач в группы. Чтобы юзер мог объединять задачки по какому-то признаку. Например: важные, срочные, рабочие, на неделю и т.д. Например вот так:

пример группировки туду-листа

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

пример клика по заголовку группы
Пример реализации: кликаем на чекбокс у родительской группы и все задачки помечаются как выполненые

Киллер-фича: группировка

А что если дать возможность объединять в группы не только одиночные задачки, но и другие группы задач? Чтобы получился этакий конструктор, где бы мы могли объявлять глобальную задачу, которая бы включала в себя ряд задач и групп задач, которые могли бы включать в себя другие группы и так далее... И в итоге мы могли бы строить целые деревья задач!

дерево из деревьев и их задач
Как бы могло выглядеть небольшое дерево задач, ветками которого могут быть как группы задач, так и отдельные задачи

Модель данных

Главный вызов в таком приложении — создать модель данных, которая бы позволяла просто работать с бесконечной вложенностью групп задач и менеджерить их. Для этого отлично подходит паттерн Компоновщик (или дерево). Он позволяет нам одинаково взаимодействовать как с одиночными задачами, так и с группами.

Объявим интерфейс задачи. Мы можем управлять её статусом готовности и менять имя.

interface TaskInterface {
getIsCompleted: () => boolean;
setIsCompleted: (isCompleted: boolean) => void;
getName: () => string;
setName: (name: string) => void;
}

Создадим класс для одиночной задачи, который реализует этот интерфейс.

class Task implements TaskInterface {
private isCompleted: boolean;
private name: string;

constructor(name: string) {
this.isCompleted = false;
this.name = name;
}

getIsCompleted() {
return this.isCompleted;
}

setIsCompleted(isCompleted: boolean) {
this.isCompleted = isCompleted;
}

getName() {
return this.name;
}

setName(name: string) {
this.name = name;
}
}

А теперь, самое интересное. Создадим класс группы задач, который будет реализовывать интерфейс задачи и меть пару дополнительных методов add и remove для того, чтобы управлять дочерним списком задач.

class TasksGroup implements TaskInterface {
private tasks: Set<Task | TasksGroup>;
private name: string;

constructor(name: string) {
this.tasks = new Set();
this.name = name;
}

add(task: Task) {
this.tasks.add(task);
}

remove(task: Task) {
this.tasks.delete(task);
}

getIsCompleted() {
if (this.tasks.size === 0) return true;
return Array.from(this.tasks).every((task) => {
return task.getIsCompleted();
});
}

setIsCompleted(isCompleted: boolean) {
Array.from(this.tasks).forEach((task) => {
task.setIsCompleted(isCompleted);
});
}

getName() {
return this.name;
}

setName(name: string) {
this.name = name;
}
}

Создадим модель данных

Используем классы для создания групп и задач.

// создадим певую группу задач
const playCat = new Task('Поиграть с котом');
const washDish = new Task('Помыть посуду');
const homeGroup = new TasksGroup('Домашние дела');

homeGroup.add(playCat);
homeGroup.add(washDish);

// создадим вторую
const dryCleaning = new Task('Забрать вещи из химчистки');
const pharmacyOrder = new Task('Забрать заказ из аптеки');
const importantGroup = new TasksGroup('Важные дела');

importantGroup.add(dryCleaning);
importantGroup.add(importantGroup);

// объединим их в общую вместе с другими задачами
const gym = new Task('Сходить в спортзал');
const article = new Task('Написать статью в блог');

const weeklyGroup = new TasksGroup('Дела на неделю');

weeklyGroup.add(gym);
weeklyGroup.add(article);
weeklyGroup.add(homeGroup);
weeklyGroup.add(importantGroup);

// пометим всю ветку домашних задач как выполненную
homeGroup.setIsCompleted(true);

Итог

Паттерн Компоновщик отлично подходит для приложений, где мы работает с древовидной структурой данных и когда необходимо реализовать одинаковую логику работы как с одиночными объектами, так и с их группами.

Комментарии