2014-11-13 4 views
5

Я работаю над дизайном API, и есть кое-что о Java и полиморфизме, о котором я не думал до сих пор. Если я создаю API, как это:Java, полиморфизм, статическая типизация и шаблон «QueryInterface»

interface FooFactory 
{ 
    public Foo getFoo(); 
} 

interface Foo 
{ 
    public void sayFoo(); 
} 

тогда единственное, что моя FooFactory реализация может полагаться, чтобы обеспечить это Foo реализации. Если я решу предоставить некоторые усовершенствованные методы, например:

interface EnhancedFoo extends Foo 
{ 
    public void patHeadAndRubBelly(); 
} 

class EnhancedFooImpl implements EnhancedFoo 
{ 
    ... implementation here ... 
} 

class EnhancedFooFactoryImpl implements FooFactory 
{ 
    @Override public EnhancedFoo getFoo() { return new EnhancedFooImpl(); } 
} 

и единственный способ мои клиенты API могут использовать интерфейс EnhancedFoo, если они получают интерфейс Foo и попытаться привести его в качестве EnhancedFoo.

Я помню, как Microsoft обрабатываются COM в интерфейсе IUnknown:

HRESULT QueryInterface(
    [in] REFIID riid, 
    [out] void **ppvObject 
); 

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

я мог бы сделать что-то подобное в типизированном образе с Java:

interface FooFactory 
{ 
    public <T extends Foo> T getFoo(Class<T> fooClass); 
} 

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

Мой вопрос, является:

  • является образцом QueryInterface разумным?
  • Должен ли я просто использовать литье?
  • или это единственный правильный способ обработки полиморфизма в API, чтобы ограничить клиентов строго использованием простых методов? (Например, способы, описанные в Foo в моем примере, а не любые EnhancedFoo методы)

Разъяснение: Реализация завод не будет известен клиентским кодом. (Предположим, что он использует инъекцию зависимостей или некоторую архитектуру поставщика услуг, такую ​​как java ServiceLoader или NetBeans Lookup.) Так что, как клиент, я не знаю, что доступно. Завод может иметь доступ к нескольким производным от Foo, и я хочу, чтобы клиент мог запросить набор функций, который он хочет, и либо он получит это, либо ему придется вернуться на базовую функциональность Foo.

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

ответ

1

Вы могли бы просто объявить завод с обобщениями, а остальное оставьте как есть:

static interface FooFactory { 
    public <T extends Foo> T getFoo(); 
} 

Тогда благодаря типа умозаключение, это будет компилировать:

FooFactory f = new EnhancedFooFactoryImpl(); 
EnhancedFoo e = f.getFoo(); 

(это может не работать до Java 8)

Если FooFactory - это не то, что вы ожидали, линия EnhancedFoo e = f.getFoo(); будет кидать ClassCastException.

+0

Тип вывода был улучшен в Java 8. – assylias

+0

Хм ... подход к исключению является нормальным для Python, но кажется неприемлемым для Java. –

+0

Я бы сказал, что это вполне уместно - лучше, чем проверка нуля – Arkadiy

1

Почему не параметризовать интерфейс Factory?

static interface FooFactory<T extends Foo> { 
    public T getFoo(); 
} 

затем:

class EnhancedFooFactoryImpl implements FooFactory<EnhancedFoo> { 

    @Override 
    public EnhancedFoo getFoo() { return new EnhancedFooImpl(); } 
} 

И конкретизации:

FooFactory<EnhancedFoo> f1 = new EnhancedFooFactoryImpl(); 
EnhancedFoo foo = f1.getFoo(); 

Усиленный завод, конечно, может использоваться там, где, как ожидается, базовый класс.

FooFactory<?> f2 = new EnhancedFooFactoryImpl(); 
Foo foo = f2.getFoo(); 

EDIT (на ответ на Ваш комментарий)

Если для вашего дизайна, лучше иметь параметризованное фабричный метод не класс завода, то это хорошая практика, чтобы определить его, как показано ниже:

Это дает вам преимущество двумя способами: 1. Вы можете контролировать, какой пользователь actall clas sa хочет создать. 2. Имея объект класса, вы можете создать его с отражением.

Так что в этом случае вам не нужно иметь специальных классов Factory для повышения Foo:

class FooFactoryImpl implements FooFactory { 

    @Override 
    public <T extends Foo> T getFoo(Class<T> c) { 
     try { 
      return c.newInstance(); 
     } catch (ReflectiveOperationException e) { 
      return null; 
     } 
    } 
} 

Тогда использование является, как показано ниже:

FooFactory ff = new FooFactoryImpl(); 
EnhancedFoo ef = ff.getFoo(EnhancedFoo.class); 
Foo f = ff.getFoo(Foo.class); 

Если некоторые реализации Foo требует параметров конструкторе всегда может поставить соответствующий, если в foctory метод и создать экземпляр объекта «вручную»:

@Override 
    public <T extends Foo> T getFoo(Class<T> c) { 

     if(SomeParametrizedFoo.class.equals(c)) { 
      SomeParamtrizedFoo spf = new SomeParamtrizedFoo("constr arg"); 
      spf.setParam(16136); 
      return (T) spf; 
     } 

     try { 
      return c.newInstance(); 
     } catch (ReflectiveOperationException e) { 
      return null; 
     } 
    } 
+0

Думаю, я думаю, что есть определенные возможности, которые клиент может пожелать использовать. Для класса 'Foo' может быть более одного расширения, поэтому я не хочу оставлять его на заводе; вместо этого он должен быть до клиента. Я недостаточно разбираюсь в дизайне API, что я не уверен на 100%, как точно сформулировать свой вопрос. –

+0

добавлено разъяснение –

+0

@JasonS Хорошо, я обратился к этому в своем редактировании. –

Смежные вопросы