Skip to content

Latest commit

 

History

History
367 lines (245 loc) · 17.7 KB

STUDENT_GUIDE.md

File metadata and controls

367 lines (245 loc) · 17.7 KB

Эффективная работа с IDE

Используйте shortcut-ы

Организация Кода

  • Называйте пулл реквест Homework + ее номер.

  • В одном пулл реквесте должен присутствовать код по соответствующей домашней работе, либо по нескольким, следующим подряд.

Типичные Ошибки

Общие Правила

Выбирайте отражающие суть названия

В идеале по названию класса/метода должно быть понятно, что он делает. Чем более конкретно — тем лучше.

Нет

boolean someAct()
boolean isSomeAct()
void manage(SmartHome home)
SmartHome read()
interface EventHandling

Да

boolean isClosed() // В классе с состоянием двери
void processEvent(Event event)

Предлагается две стратегии для названия классов с имплементаций интерфейсов:

  1. Имплементация представляет частный случай — название частного случая + название интерфейса, например interface HomeReader, class FileHomeReader, class DatabaseHomeReader.

  2. Имплементация является стандартной/единственной — название интерфейса + постфикс Impl, например interface EventLoopProcessor, class EventLoopProcessorImpl.

Называйте методы глаголом с объектом camelCase

Нет

public void Execute()

Да

public void executeSomething()

Называйте классы существительными UpperCamelCase

Нет

class ReadFile
class ReadFromFile
class CheckIsHall

Да

class FileReader
class HallChecker

Называйте пакеты строчными буквами

Нет

package ru.sbt.mipt.oop.SmartHome;

Не используйте статические методы в бизнес логике

Они хорошо применяются в объектах без состояния. Например, в утильных классах.

В такой ситуации лучше создать интерфейс и пользоваться им вместо вызова статического метода конкретного класса. Это позволит в будущем лекго заменить данную имплементацию (OCP).

Нет

public class SpecificEventCreator {
    public static Event getNextEvent() {
        ...
    }
}

public class EventCreatorConsumer {
    public void needToUseEvent() {
        Event event = SpecificEventCreator.getNextEvent();
        ...
    }
}

Да

public interface EventCreator {
    public Event getNextEvent();
}

public class EventCreatorImpl implements EventCreator {
    public Event getNextEvent() {
        ...
    }
}

public class EventCreatorConsumer {
    private final EventCreator eventCreator;

    public EventCreatorConsumer(EventCreator eventCreator) {
        this.eventCreator = eventCreator;
    }

    public void needToUseEvent() {
        Event event = eventCreator.getNextEvent();
        ...
    }
}

Передавайте зависимости снаружи

Делайте зависимости полями в классе.

Нет

public class Application {
    public void run() {
        Event event = getEvent();
        new MyEventProcessor().processEvent(event);
    }
}

Да

public class Application {
    private final EventProcessor processor;
    
    public Application(EventProcessor processor) {
        this.processor = processor;
    }

    public void run() {
        Event event = getEvent();
        processor.processEvent(event);
    }
}

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

Нет

List<EventHandler> handlers = new HandlerCreator().constructHandlers();

Да

List<EventHandler> handlers = Arrays.asList(new DoorEventHandler(), ...);

См. asList(T...)

Сделите за видимостью методов

Помните про инкапсуляцию, не нужно давать лишний доступ к внутренним переменным/методам — клиенты должны пользоваться публичным API.

Например, пользователи изменяют состояние дома посредством отправки событий. Не надо давать им возможность включать/выключать свет в доме напрямую.

Нет

...
public void handleEvent(Event event) {
    ...
    turnOffLight();
}

public void turnOffLight() {
    ...
}

Да

...
public void handleEvent(Event event) {
    ...
    turnOffLight();
}

private void turnOffLight() {
    ...
}

Конфигурируйте компоненты в main или DI фреймворке

Объекты с бизнес-логикой нужно создавать вместе с зависимостями в конфигурационном коде. Это может быть public static void main(String[] args), либо конфигурации выбранного DI фреймворка. Для этого хорошо подходят классы, у которых зависимости объявлены аргументами конструктора.

Не надо использовать отдельные Java enum классы для этой цели. Их вызовы в коде тяжело менять, искать.

Нет

public enum EventProcessors {
    DOOR_EVENT_PROCESSOR {
        @Override
        public EventProcessor createProcessor() {
            return new DoorEventProcessor();
        }
    },

    LIGHT_EVENT_PROCESSOR {
        @Override
        public EventProcessor createProcessor() {
            return new LightEventProcessor();
        }
    };

    public abstract EventProcessor createProcessor();
}

Используйте интерфейсы вместо абстрактных классов

Пункт 20 из Effective Java говорит об превосходстве интерфейса над абстрактным классов для описания абстракции. Придерживайтесь его.

Нет

public abstract class CommandSender {
    public abstract void sendCommand(Command command);
}

Да

public interface CommandSender {
    void sendCommand(Command command);
}

Задание 1

Оставляйте в репозитории только файлы с кодом

Часто под контроль версий кода попадает скомпилированный редактором байткод (в IntelliJ Idea это директорияtarget/classes). Добавьте соответствующее исключение в .gitignore.

Не давайте абстракции «течь»

От англ. leaky abstraction. Детали имплементации не должны быть отражены в публичном API. Распространенная ошибка — добавление классов специфических исключений в сигнатуру методов интерфейса. Другим имплементациям придется работать с этими нерелевантными для них исключениями.

Разделяйте изменение объектов и сценарии умного дома

Помните про SRP. Создайте по классу на обработку события открытия/закрытия двери и обработке сценария выключения света в доме.

Делегируйте обработку событий отдельным классам

Если класс события сам занимается его обработкой — это нарушение SRP. В будущем будет появляться необходимость добавлять больше обработчиков на одни и те же события.

Перенесите логику цикла обработки событий в отдельный класс

Здесь речь идет про логику по запуску генерации событий из внешнего источника и делегации обработки.

Задание 2

Задача — уйти от внешней итерации и преместить эту ответственность в объекты дома.

Что должен делать Actionable? Он должен принимать инстанс некого Action и применять его к своей внутренней структуре, которую никто кроме него не знает. Это позволяет нам легко ее менять, не затрагивая при этом других клиентов.

Что должен делать Action? Он должен принимать инстанс некого HomeComponent и делать с ним заданное действие. Это можно реализовать с помощью лямбд Java.

Кто создает Action? Обработчики событий.

Имплементируйте внутренний итератор

Разберитесь, в чем разница между внутренним и внешним итератором. Последний использовать в данном задании нельзя, поэтому не надо имплементировать Iterable.

Закройте доступ к внутренним данным

Наряду с советами по дизайну API учитывайте, что после добавления логики по совершению действий над объектами дома посредством внешнего итератора вам не нужно давать доступ к некоторым объектам дома (и тем более возможность их изменять через сеттеры). Например, прямой доступ к комнатам из дома не нужен, поскольку все действия теперь можно делать через Actionable API. Это позволяет отвязаться от внутренней структуры дома. Теперь легко при необходимости добавлять новый уровень группировки объектов. Например, объединить комнаты в этаж.

Пользуйтесь текущей иерархией объектов дома

Не стоит выдумывать новые иерархии (Interior, Premise, ...) только для группировки других объектов. Этого не требуется в задании. Здесь лишняя complexity не приносит пользы, зато усложняет чтение и восприятие кода.

Придерживайтесь предложенным структурам Action и Actionable

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

Итерируйтесь по всем составляющим

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

Задание 3

Сигнализация должна работать по приницпу сейфа в отеле:

  1. Создается без пароля
  2. При активации задается пароль
  3. Для деактивации требуется вести пароль из предыдущего шага

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

Сделайте отправку максимум одного смс из декоратора

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

Задание 4

Используйте фабрики для конвертации событий внешней бибилотеки

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

Объявляйте каждого обработчика отдельным бином

Например

@Bean
EventProcessor someEventProcessor() {
    return new SomeEventProcessor();
}

@Bean
...

Далее в бине со сторонним SensorEventsManager, где они все нужны, попрость Spring собрать все бины из конфигурации типа EventHandler автоматически:

@Bean
SensorEventsManager sensorEventsManager(Collection<EventProcessor> eventProcessors) {
    ...
}

Таким образом мы явно не харкодим обработчики в одном месте и легко выключаем любой при необходимости. Можно раскидать эти бины по разным конфигурациям.

Не объявляйте бины в конфигурации дополнительно компонентами

Объявляйте бин либо в конфигурационном классе, аннотированном @Configuration через метод с аннотацией @Bean, либо компонентом с аннотацией @Component. Это два взаимно исключающих способа объявления бинов. Для активации поиска и регистрации последних необходимо активировать сканирование.

Задание 5

  • Создайте бины для RemoteControlRegistry, имплементации RemoteControl и классов команд
  • Зарегистрируйте в имплементации RemoteControl кнопки с командами как в задании (лучше все сделать внутри конфигурации)
  • Зарегистрируйте имплементацию RemoteControl в RemoteControlRegistry.
  • Добавьте тесты по имплементации RemoteControl и командам.