Автоматически регистрировать и запускать все реализации Robot

19. Автоматически регистрировать и запускать все реализации Robot

Условие задачи:
Есть несколько Spring-компонентов с роботами. Каждый робот умеет выполнять свою работу через метод startWork().

Сейчас RobotProcessing напрямую зависит от конкретных классов Robot1, Robot2, Robot3. При добавлении нового робота приходится изменять RobotProcessing.

Необходимо изменить решение так, чтобы:

  • все роботы реализовывали общий интерфейс;

  • Spring автоматически находил все реализации этого интерфейса;

  • RobotProcessing получал их как коллекцию бинов;

  • при добавлении нового робота RobotProcessing не требовал изменений.

Код:

@Component
public class Robot1 {
    public void startWork() {
        // помыть полы
    }
}

@Component
public class Robot2 {
    public void startWork() {
        // почистить стены
    }
}

@Component
public class Robot3 {
    public void startWork() {
        // играет в футбол
    }
}

@Service
public class RobotProcessing {

    @Autowired
    private Robot1 r1;

    @Autowired
    private Robot2 r2;

    @Autowired
    private Robot3 r3;

    public void startAllRobots() {
        r1.startWork();
        r2.startWork();
        r3.startWork();
    }
}

Спойлеры к решению

Подсказки
💡 Введи общий интерфейс Robot с методом startWork().
💡 Пусть каждый робот реализует этот интерфейс и остаётся Spring-бином.
💡 Spring умеет автоматически внедрять List<Robot> со всеми найденными реализациями.
💡 Используй constructor injection вместо внедрения зависимостей напрямую в поля.

Решение
public interface Robot {
    void startWork();
}
@Component
public class Robot1 implements Robot {

    @Override
    public void startWork() {
        System.out.println("Robot1 моет полы");
    }
}
@Component
public class Robot2 implements Robot {

    @Override
    public void startWork() {
        System.out.println("Robot2 чистит стены");
    }
}
@Component
public class Robot3 implements Robot {

    @Override
    public void startWork() {
        System.out.println("Robot3 играет в футбол");
    }
}
@Service
public class RobotProcessing {

    private final List<Robot> robots;

    public RobotProcessing(List<Robot> robots) {
        this.robots = robots;
    }

    public void startAllRobots() {
        robots.forEach(Robot::startWork);
    }
}

Spring при создании RobotProcessing найдёт все бины, реализующие интерфейс:

Robot

и автоматически передаст их в:

List<Robot> robots

Поэтому RobotProcessing больше не зависит от конкретных реализаций:

Robot1
Robot2
Robot3

Чтобы добавить нового робота, достаточно создать ещё один бин:

@Component
public class Robot4 implements Robot {

    @Override
    public void startWork() {
        System.out.println("Robot4 поливает цветы");
    }
}

Менять RobotProcessing после этого не потребуется.

Аннотация @Autowired на единственном конструкторе не обязательна: Spring автоматически использует его для внедрения зависимостей.

Если порядок запуска роботов важен, его можно задать через @Order:

@Component
@Order(1)
public class Robot1 implements Robot {
    // ...
}

Spring учтёт этот порядок при формировании List<Robot>.

Такое решение соответствует принципу Open/Closed: новые реализации Robot можно добавлять без изменения класса, который их запускает.