Каков хороший дизайн для интерфейса с дополнительными компонентами? - PullRequest
5 голосов
/ 30 ноября 2010

Предположим, у меня есть интерфейс, который поддерживает несколько потенциальных операций:

interface Frobnicator {
    int doFoo(double v);
    int doBar();
}

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

Я вижу несколько способов справиться с этим. Один из способов, который, по-видимому, является основной тактикой, принятой в Java API, состоит в том, чтобы просто иметь интерфейс, как показано выше, и использовать неподдерживаемые методы, чтобы поднять UnsupportedOperationException. Недостатком этого является, однако, отсутствие быстрой сбои - клиентский код не может определить, будет ли doFoo работать, пока не попытается вызвать doFoo.

Это может быть дополнено методами supportsFoo() и supportsBar(), определенными для возврата true, если соответствующий метод do работает.

Другая стратегия заключается в разделении методов doFoo и doBar на методы FooFrobnicator и BarFrobnicator соответственно. Эти методы затем вернут null, если операция не поддерживается. Чтобы клиентский код не выполнял instanceof проверки, я определяю Frobnicator интерфейс следующим образом:

interface Frobnicator {
    /* Get a foo frobnicator, returning null if not possible */
    FooFrobnicator getFooFrobnicator();
    /* Get a bar frobnicator, returning null if not possible */
    BarFrobnicator getBarFrobnicator();
}

interface FooFrobnicator {
    int doFoo(double v);
}

interface BarFrobnicator {
    int doBar();
}

В качестве альтернативы FooFrobnicator и BarFrobnicator могут расширяться Frobnicator, а методы get* могут быть переименованы в as*.

Одной из проблем с этим является именование: Frobnicator на самом деле не является фробникатором, это способ получения фробникаторов (если я не использую as* именование). Это также становится немного громоздким. Именование может быть еще более сложным, так как Frobnicator будет получено из FrobnicatorEngine службы.

Кто-нибудь имеет представление о хорошем, желательно хорошо принятом решении этой проблемы? Есть ли подходящий шаблон дизайна? В этом случае посетитель не подходит, так как клиентскому коду необходим интерфейс определенного типа (и желательно, чтобы он работал быстро, если он не может его получить), а не диспетчеризация того, какой объект он получил. Независимо от того, поддерживаются ли разные функции, это может зависеть от разных факторов - реализация Frobnicator, конфигурация времени выполнения этой реализации (например, она поддерживает doFoo, только если присутствует какая-то системная служба для включения Foo) и т. д.

Обновление: Конфигурация во время выполнения - это еще один обезьяна в этом деле. Возможно избежать переноса типов FooFrobnicator и BarFrobnicator, чтобы избежать этой проблемы, особенно если я использую Guice-modules-as-configuration, но это усложняет другие окружающие интерфейсы (такие как фабрика / конструктор, производящий Frobnicators). на первом месте). По сути, реализация фабрики, производящей фробникаторы, настраивается во время выполнения (либо через свойства, либо через модуль Guice), и я хочу, чтобы пользователю было довольно легко сказать «подключите этого провайдера фробникаторов к этому клиенту». , Я признаю, что это проблема с потенциальными внутренними проблемами проектирования, и что я, возможно, также обдумываю некоторые из проблем обобщения, но я собираюсь для некоторой комбинации наименьшего уродства и наименьшего удивления.

Ответы [ 6 ]

4 голосов
/ 30 ноября 2010

Я вижу несколько способов справиться с этим. Один из способов, который, похоже, является общей тактикой, принятой в Java API, заключается в том, чтобы просто иметь интерфейс, как показано выше, и иметь неподдерживаемые методы, вызывающие UnsupportedOperationException. Недостатком этого является, однако, отсутствие быстрой сбои - клиентский код не может определить, будет ли doFoo работать, пока не попытается вызвать doFoo.

Как вы сказали, общая тактика заключается в использовании шаблонного метода шаблон проектирования. Отличным примером является HttpServlet.

Вот как вы могли бы достичь того же.

public interface Frobnicator {
    int doFoo(double v);
    int doBar();
}

public abstract class BaseFrobnicator implements Frobnicator {
    public int doFoo(double v) {
        throw new UnsupportedOperationException();
    }
    public int doBar() {
        throw new UnsupportedOperationException();
    }
}

/**
 * This concrete frobnicator only supports the {@link #doBar()} method.
 */
public class ConcreteFrobnicator extends BaseFrobnicator {
    public int doBar() {
        return 42;
    }
}

Клиент должен просто прочитать документы и обработать UnsupportedOperationException соответственно. Так как это RuntimeException, это идеальный случай для "ошибки программиста". Правда, это не очень быстро (то есть, не время компиляции), но это то, за что вам платят как разработчику. Просто предотврати это или поймай и смирись с этим.

3 голосов
/ 30 ноября 2010

Я думаю, у вас есть проблема, если вы говорите, что класс FooImpl реализует интерфейс Foo, но на самом деле он не реализует ВСЕ из Foo.Интерфейс должен быть контрактом, указывающим, какие методы реализует класс реализации (как минимум).

Если что-то вызывает FooFactory для получения объекта Foo, объект должен иметь возможность делать то же, что и Foos.

Итак, куда вы идете оттуда?Я бы посоветовал, чтобы ваша идея композиции была хорошей, с немного меньшим использованием наследования.

Композиция будет работать точно так же, как вы используете методы getFooFrobnicator() и getBarFrobnicator().Если вам не нравится, что ваш Frobnicator на самом деле не имеет фробниката, назовите его FrobnicatorHolder.

Наследование будет иметь ваш Frobnicator, содержащий только методы, которые есть у всех Frobnicators, и вложенный элемент.интерфейсы будут иметь дополнительные методы.

public interface Frobnicator{
   public void frobnicate();
}

public interface FooFrobnicator{
   public void doFoo();
}

public interface BarFrobnicator{
   public void doBar();
}

Затем вы могли бы использовать if (frobnicator instanceof FooFrobnicator) { ((FooFrobnicator) frobnicator).doFoo() }, и жизнь продолжалась.

Подарите композицию наследованию.Вы будете счастливее.

2 голосов
/ 30 ноября 2010

Наиболее очевидным решением является определение двух интерфейсов FooFrobnicator и BarFrobnicator.Это должно быть хорошо, так как класс может реализовывать несколько интерфейсов.

Затем вы можете предоставить супер (маркерный) интерфейс, если необходимо.

2 голосов
/ 30 ноября 2010

Как насчет простого добавления пустых нереализационных методов, а не бросания UnsupportedOperationExceptions?Таким образом, вызов неподдерживаемого метода не будет иметь побочных эффектов и не вызовет ошибку во время выполнения.

Если вы хотите узнать, поддерживает ли определенный объект метод или нет, я бы посоветовал вам разделить ваш интерфейс на два с помощью общего суперинтерфейса, а затем проверить тип любого объекта, который вы получилиопределить, какие методы поддерживаются.Это, вероятно, чище и более целесообразно, чем мое первое предложение.

interface Frobnicator {}

interface FooFrobnicator extends Frobnicator {
    void doFoo();
}

interface BarFrobnicator extends Frobnicator {
    void doBar();
}

Редактировать:

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

interface Frobnicator 
{
    boolean doFoo();
    boolean doBar();
}

class FooFrobnicator implements Frobnicator 
{
    public boolean doFoo() { code, code, code; return true; }
    public boolean doBar() { return false; }
}
0 голосов
/ 01 декабря 2010

ИМХО, вы делаете этот путь слишком сложным. Покончить с интерфейсом. Создайте базовый класс с помощью часто используемых методов (они могут даже ничего не делать в базовом классе, если вы не знаете, как их реализовать там). Затем расширьте базовый класс и внедрите / переопределите все необходимые вам методы.

0 голосов
/ 30 ноября 2010

Более простое решение - добавить supports*() методов,

Если вы считаете, что это не очень хороший стиль, тогда просто забудьте о Frobnicator (это дает вам очень мало, поскольку это не тот интерфейс, на который вы можете положиться) и кодируйте прямо против FooFrobnicator и BarFrobnicator, и если они осуществляются одним и тем же объектом, хорошо. Это также дает вам возможность требовать определенного интерфейса в клиентском коде:

interface FooFrobnicator {
    doFoo();
}

interface BarFrobnicator {
    doBar();
}

public class Client {
     ...
     FooFrobnicator fooFrobnicator;
     public void setFooFrobnicator(FooFrobnicator fooFrobnicator) {
         this.fooFrobnicator = fooFrobnicator;
     }

     BarFrobnicator barFrobnicator;
     public void setBarFrobnicator(BarFrobnicator barFrobnicator) {
         this.barFrobnicator = barFrobnicator;
     }
     ...
     public void doSomething() {
         ...
         if (fooFrobnicator != null) { ... }
         ...
         if (barFrobnicator != null) { ... }
     }
}
...
public class FrobnicatorImpl implements FooFrobnicator, BarFrobnicator { ... }
...
public void doSomething() {
    ...
    FrobnicatorImpl impl = new FrobnicatorImpl();
    Client client = new Client();        
    client.setFooFrobnicator(impl);
    client.setBarFrobnicator(impl);
    ...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...