Использование базового перечисления по умолчанию для зависимых перечислений

#java #enums

#java #перечисления

Вопрос:

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

 public enum ServiceProviderType {
    FOO, BAR;

    public ServiceProviderType checkService(String url) {
        if (url.equals("http://www.example.com")) {
            return ServiceProviderType.FOO;
        } else {
            return ServiceProviderType.BAR;
        }
    }
}
  

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

 public enum LayerServiceProviderType {
    FOO("#ff0000"),
    BAR("#ff5555");

    private String colour;

    public ServiceProviderType(String colour) {
        this.colour = colour;
    }

    public String getColour() {
        return colour;
    }

    public ServiceProviderType checkService(String url) {
        if (url.equals("http://www.example.com")) {
            return ServiceProviderType.FOO
        } else {
            return ServiceProviderType.BAR;
        }
    }
}
  

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

Моей первой мыслью было иметь интерфейс, содержащий перечисление:

 interface ServiceProvider {
    ServiceProviderType {FOO, BAR};

    ServiceProviderType getServiceProviderType();

    ServiceProvider checkService(String url);
}

public enum LayerServiceProvider implements ServiceProvider {
    FOO (ServiceProviderType.FOO, "#ff0000"),
    BAR (ServiceProviderType.BAR, "#ff0000");

    public LayerServiceProvider(ServiceProviderType serviceProviderType, String colour) {
        this.serviceProviderType = serviceProviderType;
        this.colour = colour;
    }

    @Override
    public ServiceProviderType getServiceProviderType() {
        return this.serviceProviderType;
    }

    @Override
    public ServiceProvider checkService(String url) {
        if (url.equals("http://www.example.com")) {
            return LayerServiceProviderType.FOO
        } else {
            return LayerServiceProviderType.BAR;
        }
    }
}
  

Но мне кажется чрезмерным иметь перечисление с перечислением, каждое из которых содержит один и тот же диапазон значений. Есть ли лучший способ сделать это?

Комментарии:

1. Есть ли причина, по которой вы не могли просто добавить colour ServiceProviderType ?

2. colour и другие переменные не будут общими для всех ServiceProviderType — переменные будут меняться в соответствии с тем, что мы запрашиваем у поставщика услуг. Общность — это перечисление.

3. Мне действительно непонятно, в чем проблема, с которой вы столкнулись. На самом деле это вопрос не столько о перечислениях, сколько о спецификации предметной области и дизайне модели. «Плохо спроектированный API может навсегда разрушить класс» — Дж. Блох. Возможно, перечисление — это элегантный способ (стратегические перечисления — это вещь), но, возможно, нет. Я недостаточно знаком с вашим проектом, чтобы знать, как помочь.

4. @scottb — В некоторых случаях логика разграничения между поставщиками услуг специфична для реализации этих поставщиков услуг — например, в зависимости от уровня, который мы запрашиваем у службы. Однако в некоторых случаях поведение является общим. Шаблон перечисления стратегии решает мою проблему, но делает это путем дублирования значений FOO, BAR внутри вложенного перечисления.

Ответ №1:

Возможно, шаблон посетителя — это то, что вы ищете.

Используемый с перечислением, он в основном позволяет добавлять логику, зависящую от перечисления, без использования switch операторов.

Пример:

 public enum ServiceProviderType {
    FOO {
        @Override public <T> T apply(Action<T> action) { return action.doFoo(); }
    },
    BAR {
        @Override public <T> T apply(Action<T> action) { return action.doBar(); }
    };

    public interface Action<T> {
        T doFoo();
        T doBar();
    }

    public abstract <T> T apply(Action<T> action);

    public static ServiceProviderType checkService(String url) {
        if (url.equals("http://www.example.com"))
            return FOO;
        return BAR;
    }
}

public class LayerServiceProviderType implements ServiceProviderType.Action<String> {
    @Override
    public String doFoo() {
        return "#ff0000";
    }
    @Override
    public String doBar() {
        return "#ff0000";
    }
}

public class Main {
    public static void main(String[] args) {
        ServiceProviderType type = ServiceProviderType.checkService("");

        String colour = type.apply(new LayerServiceProviderType());
    }
}
  

Комментарии:

1. Мне нравится этот шаблон, но мне интересно, как он работает в случае, когда LayerServiceProviderType должен возвращать несколько переменных. Например, случай, когда мне может понадобиться иметь colour переменную и name переменную (и многое другое).

2. В этом случае вы можете рассматривать Layer... приведенный выше класс как фабричный класс , возвращающий объект с набором методов получения, о которых идет речь. Это не ограничивается только методами получения; это могут быть поведенческие методы, что делает это более похожим на шаблон стратегии , в основном используя «фабрику» для получения «стратегии», зависящей от перечисления.