Как обеспечить реализации для перечислимых значений в Java? - PullRequest
0 голосов
/ 04 февраля 2019

У меня есть enum-класс со значениями, которые, как предполагается, будут расти со временем, и я хочу, чтобы пользователи, которые добавляют новые значения enum, также где-то обеспечивали имплементацию.Но я не уверен, как заставить их обеспечить имплементацию, поскольку имплементация будет в каком-то другом классе.Например,

public enum DayType {
    SUNDAY,
    MONDAY;
}

Ссылка в классе

class X{
DateType dateType;
..
}

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

if (x.getDateType().equals(DayType.SUNDAY)) {
...
}else if(x.getDateType().equals(DayType.MONDAY)){
..
}

Так что, если кто-то добавляет DateType, он должен быть вынуждендобавьте имплементацию в приведенную выше логику if-else.Желательно путем добавления функциональных интерфейсов, если это возможно?

Я не могу форсировать имплементацию в классе enum, так как имплементация имеет пружинные зависимости.

Ответы [ 8 ]

0 голосов
/ 04 февраля 2019

Этот ответ расширяет некоторые другие ответы, здесь продвигающие использование полиморфизма.

То, что я обычно пытаюсь сделать, чтобы избежать появления постоянно растущего переключателя / условия, - это добавить метод идентификации кинтерфейс:

public enum Day {
    SUNDAY, MONDAY
}

public interface DayProcessor {

    Day day();

    // I don't know what Days are supposed to do exactly
    Object process(Object input); 
}

Затем я создаю фабричный компонент со всеми доступными DayProcessor реализациями, подключенными к Spring:

@Component
public class DayProcessorFactory {

    @Autowired
    private List<DayProcessor> dayProcessors;

    public DayProcessor create(Day day) {
        for (DayProcessor dayProcessor: dayProcessors) {
            if (dayProcessor.day().equals(day)) {
                return dayProcessor;
            }
        }
        // DayNotFoundException extends RuntimeException
        throw new DayNotFoundException(String.format("No DayProcessor found for day %s", day));
    }
}

Это форма шаблона стратегии.

Альтернативный способ перечислить все дни - перечислить их (но тогда вам придется обновить список при создании нового дня):

private DayProcessor[] dayProcessors = new DayProcessor[] {new MondayProcessor(), new SundayProcessor()};
0 голосов
/ 04 февраля 2019

Вы можете создать интерфейс One, который будет реализован вашим классом Enum.В этом случае все перечисления должны реализовывать метод в интерфейсе.

public enum DateType implements DateTypeInterface {

    SUNDAY {
        @Override
        public void checkCondition() {
            System.out.println("Implement Sunday logic");
        }
    },
    MONDAY {
        @Override
        public void checkCondition() {
            System.out.println("Implement Monday logic.");
        }
    }
}


public interface DateTypeInterface {

     public void checkCondition();
}
0 голосов
/ 04 февраля 2019

Я не думаю, что у вас есть возможность во время компиляции применить вашу политику.Я бы предложил создать интерфейс (что-то вроде DateTypeAction) с одним методом (скажем, action()), а затем фабрику, которая производит конкретные реализации на основе значения вашего DateType (что-то вроде DateTypeActionFactory.getDateTypeAction(DateType dateType)).,На этапе инициализации вы можете выполнить все значения вашего DateType (DateType.values()) и проверить, что в вашем DateTypeActionFactory есть ненулевая реализация DateTypeAction для каждого значения.Если вы обнаружите отсутствующую реализацию для одного или нескольких значений, сгенерируйте исключение с явным сообщением об ошибке, говорящим о том, что реализация (и) отсутствует (и) и завершаются неудачно при запуске вашего приложения.Это кажется разумным.

Кстати, если вы пойдете по этому шаблону, у меня есть рекомендация: я написал java-библиотеку с открытым исходным кодом под названием MgntUtils, которая предоставляет простую инфраструктуру (очень хорошо подходящую для использования с Spring), которая имеет самонаполняющийся фабричный шаблон.Т.е. вы можете создать интерфейс и фабрику, расширяющую библиотеку, предоставляя родительские классы, и тогда каждая реализация вашего интерфейса будет автоматически вставлена ​​в вашу фабрику с заранее заданным именем.Я использовал это много раз и нашел это очень удобным.Вот ссылка на статью, описывающую всю библиотеку: Java-библиотека MgntUtils с открытым исходным кодом с фильтрацией трассировки стека, анализом Silent String, конвертером Unicode и сравнением версий .Ищите параграф

Управление жизненным циклом (самоопределяющиеся фабрики)

для краткого объяснения этой функции.Здесь вы можете прочитать обо всей идее этой функции: Ненавязчивый доступ к "Сиротам" в среде Spring. .Обе статьи также объясняют, где взять библиотеку, но вот прямые ссылки: Центральный репозиторий Maven и Github .Библиотека поставляется с хорошо написанным Javadoc

0 голосов
/ 04 февраля 2019

Альтернативой опции nullpointer является включение этой логики в само перечисление, предполагая, что это имеет смысл в вашем дизайне (если эта ответственность может лежать на перечислении типа дня).

public enum DayType {
    SUNDAY {
        @Override
        void processSomeAction(Object input) {
            super.processSomeAction(input);
            // process action with SUNDAY-specific logic
        }
    },

    MONDAY;

    //can be made abstract if there's no default implementation
    void processSomeAction(Object input) {
        // process action with default logic
    }
}

Это будетУбедитесь, что необходимость обеспечения реализации на конкретный день очевидна для разработчиков, которые вносят изменения в DayType.

А звонящему просто понадобится:

x.getDateType().processSomeAction(inputIfApplicable);
0 голосов
/ 04 февраля 2019

Реальный ответ: не делай этого.

Если у вас есть какой-то «тип», и вы знаете, что этот «тип» будет видеть новые воплощения с течением времени, которые требуют другого поведения, тогда ответом будет использование абстрактного класса и полиморфизма.

Неважно, используете ли вы цепочки if / else или операторы switch: идея, что у вас есть что-то вроде:

if (someObject.getSomething() == whatever) { then do this } else { that }

- это плохая практика!

Если вообще,Вы должны использовать такой оператор switch внутри фабрики, чтобы возвращать разные экземпляры подкласса, чтобы затем вызывать «общие» методы для таких объектов.

Ваш текущий подход: A) экстернализация знаний о внутреннем состоянии и B) также принудительное применение зависимостей несколькими способами.

Так начинается код спагетти!Лучше отступите назад и подумайте о других ООП способах решения этой проблемы!

0 голосов
/ 04 февраля 2019

Я также не уверен, что разработка enum для расширения пользователем - это хорошая идея.Это статично, как сказал @Garikai.Это приведет к запаху кода.

Если вы хотите иметь простую возможность расширить отображение от enum к функции, я бы предложил Map<DateType, Function> map = new EnumMap<DateType, Function>(DateType.class);, а затем используйте getOrDefault, чтобы убедиться, что вы получите любой вывод.

0 голосов
/ 04 февраля 2019

Перечисление предназначалось для хранения конечных значений времени компиляции (статических), поэтому динамическое добавление большего количества значений невозможно (просто поместить его там).Что касается другой части вашего вопроса, как указывал @nullpointer, то лучше использовать предложение switch.В дополнение к улучшенной ясности кода, компилятор предупреждает, если есть значения enum, для которых не объявлено case операторов (то есть, если вы опустите default)

0 голосов
/ 04 февраля 2019

Хотя я сомневаюсь, что это может быть ограничено во время компиляции.Возможный более чистый способ реализовать нечто подобное - использовать switch здесь:

switch (x.getDateType()) {
    case MONDAY: // do something
        break;
    case SUNDAY: // do something
        break;
    default:
        throw new CustomException("No implementations yet for :" + x.getDateType()); 
}
...