Используйте shortcut-ы
-
Называйте пулл реквест Homework + ее номер.
-
В одном пулл реквесте должен присутствовать код по соответствующей домашней работе, либо по нескольким, следующим подряд.
В идеале по названию класса/метода должно быть понятно, что он делает. Чем более конкретно — тем лучше.
Нет
boolean someAct()
boolean isSomeAct()
void manage(SmartHome home)
SmartHome read()
interface EventHandling
Да
boolean isClosed() // В классе с состоянием двери
void processEvent(Event event)
Предлагается две стратегии для названия классов с имплементаций интерфейсов:
-
Имплементация представляет частный случай — название частного случая + название интерфейса, например
interface HomeReader, class FileHomeReader, class DatabaseHomeReader
. -
Имплементация является стандартной/единственной — название интерфейса + постфикс 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() {
...
}
Объекты с бизнес-логикой нужно создавать вместе с зависимостями в конфигурационном коде. Это может быть 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);
}
Часто под контроль версий кода попадает скомпилированный редактором байткод (в IntelliJ Idea это директорияtarget/classes
). Добавьте соответствующее исключение в .gitignore
.
От англ. leaky abstraction. Детали имплементации не должны быть отражены в публичном API. Распространенная ошибка — добавление классов специфических исключений в сигнатуру методов интерфейса. Другим имплементациям придется работать с этими нерелевантными для них исключениями.
Помните про SRP. Создайте по классу на обработку события открытия/закрытия двери и обработке сценария выключения света в доме.
Если класс события сам занимается его обработкой — это нарушение SRP. В будущем будет появляться необходимость добавлять больше обработчиков на одни и те же события.
Здесь речь идет про логику по запуску генерации событий из внешнего источника и делегации обработки.
Задача — уйти от внешней итерации и преместить эту ответственность в объекты дома.
Что должен делать Actionable
? Он должен принимать инстанс некого Action
и применять его к своей внутренней структуре, которую никто кроме него не знает. Это позволяет нам легко ее менять, не затрагивая при этом других клиентов.
Что должен делать Action
? Он должен принимать инстанс некого HomeComponent
и делать с ним заданное действие. Это можно реализовать с помощью лямбд Java.
Кто создает Action
? Обработчики событий.
Разберитесь, в чем разница между внутренним и внешним итератором. Последний использовать в данном задании нельзя, поэтому не надо имплементировать Iterable.
Наряду с советами по дизайну API учитывайте, что после добавления логики по совершению действий над объектами дома посредством внешнего итератора вам не нужно давать доступ к некоторым объектам дома (и тем более возможность их изменять через сеттеры). Например, прямой доступ к комнатам из дома не нужен, поскольку все действия теперь можно делать через Actionable
API. Это позволяет отвязаться от внутренней структуры дома. Теперь легко при необходимости добавлять новый уровень группировки объектов. Например, объединить комнаты в этаж.
Не стоит выдумывать новые иерархии (Interior
, Premise
, ...) только для группировки других объектов. Этого не требуется в задании. Здесь лишняя complexity не приносит пользы, зато усложняет чтение и восприятие кода.
Эти две абстракции, описанные в задании, максимально простые и, благодаря своей абстрактности, позволяют решать широкий спектр задач. Помните про SRP. Для выполнения этого задания вам не нужно ничего в них добавлять — только имплементировать.
В имплементации внутреннего итератора внутри композитного объекта не забывайте выполнять заданное действие над всеми внутренними объектами.
Сигнализация должна работать по приницпу сейфа в отеле:
- Создается без пароля
- При активации задается пароль
- Для деактивации требуется вести пароль из предыдущего шага
Также добавьте возможность деактивировать сигнализацию с помощью специального события.
Декоратор должен прерывать обработку всех событий раз и до корректного ввода кода деактивации сигнализации. При этом смс-оповещение должно отправляться всего один раз, а не для каждого обработчика.
Не используйте switch по строковым названиям событий для конвертации. Фабрики можно объединить через цепочку ответственности, либо через маппинг идентификаторов внешних событий на фабрики.
Например
@Bean
EventProcessor someEventProcessor() {
return new SomeEventProcessor();
}
@Bean
...
Далее в бине со сторонним SensorEventsManager
, где они все нужны, попрость Spring
собрать все бины из конфигурации типа EventHandler
автоматически:
@Bean
SensorEventsManager sensorEventsManager(Collection<EventProcessor> eventProcessors) {
...
}
Таким образом мы явно не харкодим обработчики в одном месте и легко выключаем любой при необходимости. Можно раскидать эти бины по разным конфигурациям.
Объявляйте бин либо в конфигурационном классе, аннотированном @Configuration
через метод с аннотацией @Bean
, либо компонентом с аннотацией @Component
. Это два взаимно исключающих способа объявления бинов. Для активации поиска и регистрации последних необходимо активировать сканирование.
- Создайте бины для
RemoteControlRegistry
, имплементацииRemoteControl
и классов команд - Зарегистрируйте в имплементации
RemoteControl
кнопки с командами как в задании (лучше все сделать внутри конфигурации) - Зарегистрируйте имплементацию
RemoteControl
вRemoteControlRegistry
. - Добавьте тесты по имплементации
RemoteControl
и командам.