Конструктор для интерфейса/абстрактного класса с использованием дженериков Java

Обратите внимание на обновления, мой вопрос был сформулирован нечетко. Извините за это.

Предположим, у нас есть следующий код:

class Foo extends/implements AnAbstractClass/AnInterface { /* to make sure the constructor with int as input is implemented */ 
    Foo(int magicInt) { magicInt + 1; /* do some fancy calculations */ }
}

class Bar extends/implements AnAbstractClass/AnInterface { /* to make sure the constructor with int as input is implemented */ 
    Bar(int magicInt) { magicInt + 2; /* do some fancy calculations */ }
}

class Factory<T extends/implements AnAbstractClass/AnInterface> {
    int magicInt = 0; 

    T createNewObject() {
        return new T(magicInt) // obviously, this is not working (*), see below
    }
}

/* how it should work */
Factory<Foo> factory = new Factory<Foo>();
factory.createNewObject() // => Foo with magicInt = 1

Factory<Bar> factory = new Factory<Bar>();
factory.createNewObject() // => Bar with magicInt = 2

В позиции (*) не знаю что делать. Как я могу убедиться, что конструктор с такой сигнатурой ...(int magicInt) реализован? я не могу определить

  1. конструктор с определенной сигнатурой в интерфейсе

    interface AnInterface {
        AnInterface(int magicInt);
    }
    
  2. абстрактный класс, реализующий определенный конструктор

    abstract class AnAbstractClass {
        abstract AnAbstractClass(int magicInt);
    }
    

    и здесь явно отсутствует требование реализованного конструктора в подклассах:

    abstract class AnAbstractClass {
       AnAbstractClass(int magicInt) {}
    }
    
  3. статический метод внутри интерфейса или абстрактный класс, который можно переопределить для каждой реализации AnInterface или AnAbstractClass (думаю заводской шаблон)

Каков путь?


person Michael Dorner    schedule 02.11.2015    source источник
comment
Я не понимаю, в чем ваш вопрос... Чего именно вы пытаетесь достичь и почему это не работает?   -  person Cedric Reichenbach    schedule 02.11.2015
comment
Я думаю, что в желаемом коде результата есть что-то странное. SampleSource имеет параметр, расширяющий SampleFactory. Затем в getCurrentSample() вы вызываете эту фабрику образцов, чтобы создать образец, который должен иметь тот же тип, что и SampleFactory. Итак, создание образца дает вам фабрику образцов?   -  person Timo    schedule 02.11.2015
comment
Ну, так как статические методы Java 8 разрешены в интерфейсах.   -  person Flown    schedule 02.11.2015
comment
Кажется странным иметь классы, которые расширяют Sample и реализуют SampleFactory...   -  person Thomas    schedule 02.11.2015
comment
@Flown ему не нужен статический метод в интерфейсе, он хочет использовать интерфейс для принудительного применения класса, который реализует этот интерфейс, для его наличия.   -  person user902383    schedule 02.11.2015
comment
Насколько логично, что у вас есть что-то, что также является собственной фабрикой? Похоже, в вашем объектно-ориентированном дизайне что-то не так.   -  person Erwin Bolwidt    schedule 02.11.2015
comment
Я подумал, что это немного уродливо, когда объект создает другие объекты. Но я открыт для всех идей.   -  person Michael Dorner    schedule 02.11.2015
comment
Вы думали, что это уродливо, но все же решили реализовать шаблон Factory, т.е. явно что? Любопытный.   -  person Andreas    schedule 02.11.2015
comment
Нет, в интерфейсе не может быть конструкторов. Конструктор вызывается, когда экземпляр объекта создается оператором new, и вы не можете создать экземпляр интерфейса.   -  person Andreas    schedule 02.11.2015
comment
Из-за стирания типов фактический тип T во время выполнения неизвестен, поэтому T.class не будет работать.   -  person Andreas    schedule 02.11.2015
comment
@Andreas Re «вы не можете создать экземпляр интерфейса»: Ну, с анонимные классы можно.   -  person Gerold Broser    schedule 02.11.2015
comment
@GeroldBroser Это не создание экземпляра интерфейса. Это определение нового безымянного класса, реализующего интерфейс и создание экземпляра (анонимного) класса, все в одном. Ссылка, которую вы дали, даже говорит об этом: Они позволяют вам объявлять и создавать экземпляр класса одновременно.   -  person Andreas    schedule 02.11.2015
comment
Спасибо за все Ваши ответы. Я вижу проблему в том, что вопрос был не очень хорошо сформулирован. Я попытался улучшить сам вопрос.   -  person Michael Dorner    schedule 02.11.2015
comment
Я адаптировал свой ответ к новой версии вашего вопроса.   -  person Thomas    schedule 03.11.2015
comment
На самом деле было бы интересно, почему так много голосов против... :-/   -  person Michael Dorner    schedule 06.11.2015
comment
Мне кажется, что если вам приходится думать так сложно, чтобы придумать причудливое или умное решение, то решение, скорее всего, будет хрупким или сложным в обслуживании. Наверняка для вашей проблемы существует лучшая декомпозиция, которая позволила бы использовать уже хорошо зарекомендовавшие себя, проверенные шаблоны.   -  person scottb    schedule 07.11.2015


Ответы (3)


Похоже, вы действительно ищете решение, как написать универсальный фабричный метод без множества блоков if/else и без написания одного блока в каждом классе. Таким образом, рассмотрите возможность использования отражения, как в следующем коде:

interface Interface {
}

class Foo implements Interface {
    Foo(int magicInt) { magicInt = magicInt + 1; /* do some fancy calculations */ }
}

class Bar implements Interface {
    Bar(int magicInt) { magicInt = magicInt + 2; /* do some fancy calculations */ }
}

class Factory<T extends Interface> {
    int magicInt = 0; 

    public T createNewObject(Class<T> typeToMake) {
        try {
            T t = createNewObjectWithReflection(typeToMake);
            return t;
        } catch (Exception e) {
            throw new RuntimeException("Construction failed!", e);
        }
    }

    private T createNewObjectWithReflection(Class<T> typeToMake) throws Exception {
        // find the constructor of type to make with a single int argument
        Constructor<T> magicIntConstructor = typeToMake.getDeclaredConstructor(Integer.TYPE);
        // call the constructor with the value of magicInt
        T t = magicIntConstructor.newInstance(magicInt);
        return t;
    }
}

/* Name of the class has to be "Main" only if the class is public. */
class Ideone
{
    public static void main (String[] args) throws java.lang.Exception
    {
        Factory<Foo> fooFactory = new Factory<Foo>();
        Foo foo = fooFactory.createNewObject(Foo.class);
        System.out.println(foo);

        Factory<Bar> barFactory = new Factory<Bar>();
        Bar bar = barFactory.createNewObject(Bar.class);
        System.out.println(bar);
    }
}

Вы можете запустить демонстрацию в IDEOne здесь.

person entpnerd    schedule 07.11.2015
comment
Да, это то решение, которое я ищу. Но так ли это уродливо в Java? - person Michael Dorner; 07.11.2015
comment
Довольно много, да. Я отредактировал сообщение с кодом, чтобы немного привести код в порядок, но отражение в целом не всегда красиво. - person entpnerd; 07.11.2015
comment
Нет причин для того, чтобы сама фабрика была универсальной. Было бы достаточно иметь метод в фабричном дженерике. Я также думаю, что вы, вероятно, могли бы добиться той же функциональности, используя TypeLiteral с привязкой Factory для Guice, используя вспомогательное внедрение. Это делает ваш код немного чище. - person Nathan; 07.11.2015
comment
Но в значительной степени независимо от того, какое решение вы выберете, вам понадобится какое-то автоматическое тестирование отражений, чтобы убедиться, что вы охватили все свои варианты использования. В противном случае можно создать новый подкласс, который не соответствует ожиданиям/требованиям вашей фабрики, и тогда произойдет сбой. Однако одним из серьезных ограничений этого подхода является то, что он больше не работает, если Foo или Bar требуют разных параметров в своем конструкторе. Если для Foo требуется соединение с БД, а для Bar требуется клиент WS, тогда конструкторы будут отличаться, и этот подход больше не будет жизнеспособным. - person Nathan; 07.11.2015
comment
Спасибо за ваши комментарии, Натан. Справедливый комментарий о самом фабричном классе, который не должен быть универсальным - я просто попытался сделать так, чтобы все классы как можно ближе соответствовали исходному фабричному классу в вопросе. Я рассматривал возможность использования TypeLiteral, но казалось, что единственный возможный способ вернуть информацию о типе — это передать TypeLiteral в фабричный метод вместо класса. Поскольку проблема заключалась в создании экземпляра нового класса, передача класса казалась более простой, потому что, если я передаю TypeLiteral, я просто использую его, чтобы вернуть объект класса для вызова конструктора. - person entpnerd; 07.11.2015
comment
Ваше решение станет еще проще, если мы вообще избавимся от параметра в конструкторе. Если мы определим метод initializeMagicNumber(int) в абстрактном классе/интерфейсе, то мы можем просто вызвать getInstance для класса, чтобы создать экземпляр нового объекта с помощью конструктора по умолчанию. Когда у нас есть объект, мы можем инициализировать его на фабрике. Это, конечно, оставляет открытой возможность того, что кто-то просто создаст новый объект, не инициализировав его должным образом, но это уменьшит объем кода, который необходимо написать, предполагая, что все реализации не имеют дополнительных зависимостей. - person Nathan; 07.11.2015
comment
@MichaelDorner, спасибо за награду. Если у вас есть дополнительные вопросы, пожалуйста, не стесняйтесь спрашивать. :-) - person entpnerd; 09.11.2015

Я действительно не вижу, чтобы ваша идея работала.

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

Я бы лучше:

  1. иметь один метод в вашем фабричном классе для каждого типа объекта, который вы хотите построить
  2. и, возможно, вместо того, чтобы иметь определенное поведение в конструкторах, иметь один общий конструктор в родительском абстрактном классе и один абстрактный метод, который выполняет причудливые вычисления (но это действительно предпочтение стиля).

Что приведет к чему-то вроде строк:

abstract class AbstractSample {
    private int magicInt;

    public AbstractSample(int magicInt) {
        this.magicInt = magicInt;
    }

    protected int getMagicInt() {
        return magicInt;
    }

    public abstract int fancyComputation();

}

public class Foo extends AbstractSample {
    public Foo(int magicInt) {
        super(magicInt)
    }

    public int fancyComputation() {
        return getMagicInt() + 1;
    }
}

public class Bar extends AbstractSample {
    public Bar(int magicInt) {
        super(magicInt)
    }

    public int fancyComputation() {
        return getMagicInt() + 2;
    }
}

public class SampleFactory {
    private int magicInt = 0;

    public Foo createNewFoo() {
        return new Foo(magicInt);
    }

    public Bar createNewBar() {
        return new Bar(magicInt);
    }
}

Ответ на предыдущую версию вопроса может быть удален, если обновленный ответ удовлетворяет OP

Определенно странно иметь классы, которые расширяют Sample и реализуют SampleFactory...

Я бы предпочел что-то вроде:

class Sample { 
    protected Sample() { /* ... */ }
}

interface SampleFactory<T extends Sample> {
    T createSample(final int i);
}

class AccelerationSample extends Sample {
    public AccelerationSample(final int i) { /* do some fancy int calculations*/ }
}

class OrientationSample extends Sample {
    private OrientationSample (final int i) { /* do some fancy int calculations*/ }
}

abstract class SampleSource<T extends Sample> {
    int magicInt; 
    SampleFactory<T> sampleFactory;
    T getCurrentSample() {
       return sampleFactory.createSample(magicInt);
    }
}

class AccelerationSampleSource extends SampleSource<AccelerationSample> {
    SampleFactory<AccelerationSample> sampleFactory = new SampleFactory<> {
       public AccelerationSample createSample(final int i) {
          return new AccelerationSample(i);
       }
    }
}

class OrientationSampleSource extends SampleSource<OrientationSample> {
    SampleFactory<OrientationSample> sampleFactory = new SampleFactory<> {
       public OrientationSample createSample(final int i) {
          return new OrientationSample(i);
       }
    }
}

Еще чище было бы использовать именованные фабрики, такие как

public AccelerationSampleFactory implements SampleFactory<AccelerationSample> {
    public AccelerationSample createSample(final int i) {
        return new AccelerationSample(i);
    }
 }

Который вы могли бы затем использовать как

class AccelerationSampleSource extends SampleSource<AccelerationSample> {
    SampleFactory<AccelerationSample> sampleFactory = new AccelerationSampleFactory();
}   
person Thomas    schedule 02.11.2015
comment
Действительно, извините... Невозможно определить статические методы в интерфейсе... Я соответственно обновил свой ответ. (Нет необходимости, чтобы метод был статическим, если вы хотите, чтобы он был универсальным). - person Thomas; 02.11.2015
comment
(чтобы быть более конкретным, в java 8 вы МОЖЕТЕ иметь статические методы в интерфейсах, но определенно не статические методы, возвращающие тип экземпляра) - person Thomas; 03.11.2015
comment
Спасибо за ваше решение, но тогда у меня есть статический фабричный метод для каждого класса (в моем случае у меня были бы десятки таких). Я думал, поэтому были введены дженерики. :) - person Michael Dorner; 05.11.2015
comment
Более того, в вашем решении больше нет полиморфизма. - person Michael Dorner; 05.11.2015
comment
Собственно, это то, о чем я упоминал во вступлении. Шаблон Factory обычно направлен на наличие фабричного метода для каждого класса. Существует некоторый полиморфизм в виде переопределения методов. Обобщения полезны во многих контекстах, но шаблон Factory просто не является одним из них ИМХО. Однако вы можете использовать интроспекцию для вызова конструктора на вашей фабрике, но мне это решение не очень нравится. - person Thomas; 05.11.2015

Как вы заметили, ни одна из трех идей в вопросе не поддерживается (конструктор с определенной сигнатурой в интерфейсе, абстрактный класс, обеспечивающий определенный конструктор, или статический метод в интерфейсе или абстрактном классе)

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

public interface AnInterface {
    int fancyComputation();
}
public interface IFooBarFactory<T extends AnInterface> {
    T create(int magicNumber);
}

IFooBarFactory имеет 2 конкретные реализации

public class BarFactory implements IFooBarFactory<Bar> {
    public Bar create(int magicNumber) {
        return new Bar(magicNumber);
    }
}
public class FooFactory implements IFooBarFactory<Foo> {
    public Foo create(int magicNumber) {
        return new Foo(magicNumber);
    }
}

Затем используйте шаблон стратегии (https://en.wikipedia.org/wiki/Strategy_pattern). чтобы получить правильный завод. Затем используйте эту фабрику с известным интерфейсом для производства вашего объекта с правильным значением (и любыми дополнительными значениями, которые требуются для производства объекта).

    FooBarFactory fooBarFactory = new FooBarFactory();
    IFooBarFactory<T> factory = fooBarFactory.createFactory(typeOfAnInterface);
    T impl = factory.create(magicNumber);

С конкретными реализациями

public class Bar implements AnInterface {
    private final int magicInt;
    public Bar(int magicInt) {
        this.magicInt = magicInt;
    }
    public int fancyComputation() {
        return magicInt + 2;
    }
}
public class Foo implements AnInterface {
    private final int magicInt;
    public Foo(int magicInt) {
        this.magicInt = magicInt;
    }
    public int fancyComputation() {
        return magicInt + 1;
    }
}

следующий код:

public static void main(String ... parameters) {
    test(Foo.class);
    test(Bar.class);
}
private static <T extends AnInterface> void test(Class<T> typeOfAnInterface) {
    T impl = createImplForAnInterface(typeOfAnInterface, 10);
    System.out.println(typeOfAnInterface.getName() + " produced " + impl.fancyComputation());
}
private static <T extends AnInterface> T createImplForAnInterface(Class<T> typeOfAnInterface, int magicNumber) {
    FooBarFactory fooBarFactory = new FooBarFactory();
    IFooBarFactory<T> factory = fooBarFactory.createFactory(typeOfAnInterface);
    T impl = factory.create(magicNumber);
    return impl;
}

отпечатки

Foo produced 11
Bar produced 12

Это дает ряд преимуществ по сравнению с решением с самоанализом или статическими фабриками. Вызывающему не нужно знать, как производить какие-либо объекты, а вызывающему не требуется знать или заботиться о том, является ли метод «правильным» методом для использования для получения правильного типа. Все вызывающие объекты просто вызывают один общедоступный/известный компонент, который возвращает "правильную" фабрику. Это делает ваши вызывающие объекты более чистыми, потому что они больше не тесно связаны с конкретными реализациями AnInterface для типов FooBar. Им нужно только беспокоиться о том, что «мне нужна реализация AnInterface, которая потребляет (или обрабатывает) этот тип». Я знаю, что это означает, что у вас есть два "фабричных" класса. Один для получения правильной фабрики, а другой фактически отвечает за создание конкретных типов Foo и Bar. Однако вы скрываете эту деталь реализации от вызывающих объектов с помощью дополнительного уровня абстракции (см. метод createImplForAnInterface).

Этот подход будет особенно полезен, если вы обычно используете какую-либо форму внедрения зависимостей. Моя рекомендация точно соответствует внедрению с помощью Guice (https://github.com/google/guice/wiki/AssistedInject) или аналогичная идея в Spring (Возможно ли и как сделать Assisted Injection в Spring?).

Это означает, что вам нужно иметь несколько фабричных классов (или правил привязки внедрения зависимостей для Guice), но каждый из этих классов небольшой, простой и простой в обслуживании. Затем вы пишете небольшой тест, который извлекает все классы, реализующие AnInterface, и проверяете, что ваш компонент, реализующий шаблон стратегии, охватывает все случаи (через отражение — я бы использовал класс Reflections в org.reflections:reflections). Это дает вам полезную абстракцию кода, которая упрощает использование этих объектов, уменьшая избыточный код, ослабляя тесную связь компонентов и не жертвуя полиморфизмом.

person Nathan    schedule 06.11.2015