Помогите с задачей на паттерны проектирования на Java

Рейтинг: 1Ответов: 4Опубликовано: 14.01.2023

Есть задача: Разработать класс Светофор, у которого должен быть метот next(), при вызове которого должна загораться текущая секция светофора. Но прикол в другом, надо решить ее без if, for, while, switch. Также обязательное условие: добавление секций возможно без переписывания класса Светофор.

Мой вариант без применения паттернов:

Светофор:

public class TrafficLight {
  List<ISection> section = new ArrayList<>();
  int index = 0;

  public void addSection(ISection section) {
    this.section.add(section);
  }

  public void next (){
    section.get(index++%section.size()).light();
  }
}

Секция светофора:

public class Section implements ISection{
 String color;

  public Section(String color) {
    this.color = color;
  }

  @Override
  public void light() {
    System.out.println(color);
  }
}

Решение рабочее, но в жизни желтый горит и после красного, и после зеленого, а у меня не так. Думаю что добавление 4-ой секции недопустимо, поэтому прошу помочь мне реализовать "правильный" светофор с соблюдением условий задачи и возможным применением какого-то паттерна поведения.

Ответы

▲ 1Принят

Это всё можно сделать классически через паттерн состояния.

public interface TrafficLight {

    void lightUp();

    void setState(LightState lightState);

}
public class TrafficLightImpl implements TrafficLight {

    private LightState currentState;

    public TrafficLightImpl(LightState startState) {
        this.currentState = startState;
    }

    @Override
    public void lightUp() {
        currentState.lightUp();
        currentState.goToNextState(this);
    }

    @Override
    public void setState(LightState lightState) {
        currentState = lightState;
    }
}
public enum LightState {

    GREEN("Зелёный") {
        @Override
        public void goToNextState(TrafficLight trafficLight) {
            trafficLight.setState(PRE_YELLOW);
        }
    },
    PRE_YELLOW("Жёлтый") {
        @Override
        public void goToNextState(TrafficLight trafficLight) {
            trafficLight.setState(RED);;
        }
    },
    RED("Красный") {
        @Override
        public void goToNextState(TrafficLight trafficLight) {
            trafficLight.setState(POST_YELLOW);;
        }
    },
    POST_YELLOW("Жёлтый") {
        @Override
        public void goToNextState(TrafficLight trafficLight) {
            trafficLight.setState(GREEN);;
        }
    },
    INFINITY_YELLOW("Мигающий жёлтый") {
        @Override
        public void goToNextState(TrafficLight trafficLight) {
            // no logic
        }
    };

    private final String color;

    LightState(String color) {
        this.color = color;
    }

    public abstract void goToNextState(TrafficLight trafficLight);

    public void lightUp() {
        System.out.println(color);
    }
}

Этот паттерн подразумевает то, что каждое состояние знает куда ему переходить дальше. То есть, если у нас сейчас зелёный, то программа должна понимать, что дальше - жёлтый.

В более сложных версиях у каждого состояния должен быть абстрактный метод не только goToNextState(), но и lightUp(), чтобы реализация поведения была разная (в моём случае нет необходимости). Хотя, если добавить ещё одно состояние OFF, то есть выключен, то при вызове метода lightUp() будет как раз нужно отдельное поведение, но это можно решить добавлением следующего состояния в LightState:

    OFF("") {
        @Override
        public void goToNextState(TrafficLight trafficLight) {
            // no logic
        }

        @Override
        public void lightUp() {
            System.out.println("Светофор выключен!");
        }
    };

Тестирование программы:

    public static void main(String[] args) {
        final TrafficLight trafficLight = new TrafficLightImpl(LightState.GREEN);

        trafficLight.lightUp(); // Зелёный
        trafficLight.lightUp(); // Жёлтый
        trafficLight.lightUp(); // Красный
        trafficLight.lightUp(); // Жёлтый
        trafficLight.lightUp(); // Зелёный
        trafficLight.lightUp(); // Жёлтый
        trafficLight.lightUp(); // Красный
        trafficLight.lightUp(); // Жёлтый
        trafficLight.lightUp(); // Зелёный
        trafficLight.setState(LightState.INFINITY_YELLOW);
        trafficLight.lightUp(); // Жёлтый
        trafficLight.lightUp(); // Жёлтый
        trafficLight.lightUp(); // Жёлтый
        trafficLight.lightUp(); // Жёлтый
    }

P.S. Я использовал enum для большей наглядности, чтобы на расписывать тут по сотни классов. В реальности можете сделать это не через enum, а просто через обычное наследование - ничего принципиально не измениться.

▲ 1

Следует различать набор секций -- в простейшем случае 3 для обычного светофора -- и набор состояний этого светофора.

В частности, в заданном состоянии светофора может быть дополнительная информация, например длительность его горения, моргание и т.п.

И именно с состояниями светофора должен работать метод next, так как их проще сконфигурировать при "прямом" (от красного к зеленому) и "обратном" (от зеленого к красному) ходе.

Например, также просто "присвоить" светофору единственное состояние, например мигающий желтый.

enum Light {
    RED, AMBER, GREEN,                   // основные цвета
    RED_GREEN_LEFT, RED_GREEN_RIGHT,     // доп. секции
    ;

    @Override
    public String toString() {
        return name().toLowerCase();
    }
}

public class LightState {
    final Light light;
    boolean blink;
    int seconds;

    static LightState of(Light light, int sec) {
        return of(light, sec, false);
    }

    static LightState of(Light light, int sec, boolean blink) {
        return new LightState(light, sec, blink);
    }

    LightState(Light light, int sec, boolean blink) { 
        this.light = light;
        this.seconds = sec;
        this.blink = blink;
    }

    public void light() {
        System.out.printf("%s for %d sec%s%n", light, seconds, blink ? " blink" : "");
    }
}

public class TrafficLight {
    List<Light> lights;        // просто огни светофора
    List<LightState> states;   // состояния

    // конструктор

    int index = 0;

    public void next () {
        states.get(index++ % states.size()).light();
    }
}

Тест:

TrafficLight basic = new TrafficLight(
    Arrays.asList(Light.RED, Light.AMBER, Light.GREEN),
    Arrays.asList(of(Light.RED, 45), of(Light.AMBER, 15), of(Light.GREEN, 40), of(Light.GREEN, 5, true), of(Light.AMBER, 15))
);
▲ 0

Решение 1
Возможно стоит сделать массив algorithm и в методе next итерироваться по нему. А в элементах массива будут индексы элементов списка section.

Решение 2
Можно сделать нового наследника ISection, который будет указывать на другой ISection. Но так всё равно добавлять новый элемент в список.

▲ 0

А чем итератор плох? Его просто нужно сделать циклическим, что в целом не сложно.

import java.util.Iterator;

public class TrafficLight {

    private final Iterator<Colors> iterator = new Sections(Colors.values()).iterator();

    public void next() {
        System.out.println(iterator.next());
    }
}

class Sections implements Iterable<Colors> {

    private final Colors[] colors;
    private int i;

    public Sections(Colors... colors) {
        this.i = 0;
        this.colors = colors;
    }

    @Override
    public Iterator<Colors> iterator() {
        return new Iterator<Colors>() {
            @Override
            public boolean hasNext() {
                return true;
            }
            @Override
            public Colors next() {
                try {
                    return colors[i++];
                } catch (ArrayIndexOutOfBoundsException e) {
                    i = 0;
                    return colors[i++];
                }
            }
        };
    }
}

enum Colors {
    GREEN, YELLOW, RED;
}