Как заставить два класса, которые имеют метод с одинаковым именем, сигнатурой и типом возврата, вести себя так, как если бы они реализовывали один и тот же интерфейс - PullRequest
0 голосов
/ 28 ноября 2018

Я объясню свой вопрос на примере.

У меня есть два класса NumberGeneratorApple и NumberGeneratorOrange.Оба они имеют метод с одинаковой сигнатурой и типом возврата public Integer getNumber().Проблема в том, что они не реализуют один и тот же интерфейс, хотя это было бы логично.Я мог бы создать интерфейс с этим методом и изменить эти классы для его реализации, но я не хочу этого делать.Вот почему.Эти классы генерируются автоматически, скажем, из xml.В реальном примере есть десятки таких классов.Мы должны генерировать их время от времени, и это перезаписывает старые.Я не хочу менять каждый класс вручную, чтобы реализовать интерфейс после каждого поколения.Также может быть неочевидным, что это должно быть сделано для какого-то другого человека, работающего над тем же проектом, и даже для меня через некоторое время.
Вот первый класс:

public class NumberGeneratorApple {
    public Integer getNumber(){
        return 9;
    }
}  

и второй:

public class NumberGeneratorOrange {
    public Integer getNumber(){
        return 19;
    }
}

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

public class ClassThatOperates<T extends NumberGeneratorInterface> {
    T generator;

    public ClassThatOperates(T generator) {
        this.generator = generator;
    }

    public void doSomeOperation(){
        //Obviously it won't work for NumberGeneratorApple and NumberGeneratorOrange
        // because they do not implement NumberGeneratorInterface
        //And if I change "<T extends NumberGeneratorInterface>" to "<T>"
        // I cannot write method call "generator.getNumber()"
        System.out.print("The number with constant is: ");
        System.out.println(generator.getNumber() + 5);
    }
}

Это интерфейс (я знаю, что public там не нужен, и он есть по умолчанию. Я просто хочу подчеркнуть это):

public interface NumberGeneratorInterface {
    public Integer getNumber();
}

Как видите, это невозможно сделать, потому что ни NumberGeneratorApple, ни NumberGeneratorOrange не реализуют NumberGeneratorInterface.Однако я нашел какое-то решение, но, следуя своему инстинкту, я думаю, что оно довольно плохое.Я сделал классы-оболочки:

public class NumberGeneratorAppleWrapper extends NumberGeneratorApple implements NumberGeneratorInterface {
}

и

public class NumberGeneratorOrangeWrapper extends NumberGeneratorOrange implements NumberGeneratorInterface {
}

Это немного сложно.Сначала это может быть неочевидно, но когда вы вызываете getNumber () для объекта одного из этих классов, вы на самом деле вызываете что-то вроде этого:

@Override
public Integer getNumber() {
    return super.getNumber();
}

Теперь я могу назвать это так:

public class Main {
    public static void main(String[] args) {
        ClassThatOperates<NumberGeneratorAppleWrapper> classThatOperatesApple = new ClassThatOperates<>(new NumberGeneratorAppleWrapper());
        ClassThatOperates<NumberGeneratorOrangeWrapper> classThatOperatesOrange = new ClassThatOperates<>(new NumberGeneratorOrangeWrapper());
        classThatOperatesApple.doSomeOperation();
        classThatOperatesOrange.doSomeOperation();
    }
}

И я получаю следующий вывод:

The number with constant is: 14
The number with constant is: 24

Преимущество такого подхода вместо добавления вручную implements NumberGeneratorInterface к каждому сгенерированному классу состоит в том, что нам не нужно повторять одну и ту же работу послекаждое поколение (которое переопределяет старые классы).Мы только должны добавить новую оболочку, когда генерация приводит к появлению какого-то нового, дополнительного класса.

Я знаю, что в таком сценарии я могу избавиться от обобщений в ClassThatOperates и просто объявить NumberGeneratorInterface generator; без <T extends...>и так далее (и я даже должен), чтобы код был проще, но я хочу сделать этот пример очень похожим на то, что я нашел в каком-то реальном проекте.Я написал: у меня есть, я сделал, я подошел и т. Д., Но на самом деле он основан на уже существующем коде, который я нашел в каком-то проекте.

Есть мои вопросы:
1. Есть лилучшее решение?
2. Является ли это решение так называемым «дурным вкусом»?
3. Если мне придется использовать такое решение, возможно, весь мой подход неверен?
4. Если лучшего решения не существуетдаже если весь подход неверен, что можно улучшить в этом коде (включая избавление от обобщений)?

Ответы [ 3 ]

0 голосов
/ 28 ноября 2018
  1. Есть ли лучшее решение?

Ваше решение мне подходит.Вы использовали Pattern Adapter , который использует существующие функциональные возможности для соответствия с другим не связанным интерфейсом.Не изменяя NumberGeneratorApple и NumberGeneratorOrange, вы адаптировали функциональность этих классов к NumberGeneratorInterface.

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

public class NumberGeneratorApple {
    public Integer getAppleNumber(){
        return 9;
    }
}

Тогда ваш класс адаптера будет явно вызывать getAppleNumber при реализации интерфейса.

public class NumberGeneratorAppleWrapper extends NumberGeneratorApple implements NumberGeneratorInterface {
    @Override
    public Integer getNumber() {
        return getAppleNumber();
    }
}
Является ли это решение так называемым "плохим вкусом"?

Это решение не оставляет неприятного вкуса.Шаблон адаптера - это хорошо разработанный шаблон разработки программного обеспечения.

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

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

public class NumberGeneratorApple implements NumberGeneratorInterface {
    @Override
    public Integer getNumber() {
        return 9;
    }
}

Или, если сигнатура метода отличается:

public class NumberGeneratorApple implements NumberGeneratorInterface {
    public Integer getAppleNumber() {
        return 9;
    }

    @Override
    public Integer getNumber() {
        return getAppleNumber();
    }
}
Если нет лучшего решения, даже если весь подход неверен, что можно улучшить в этом коде (включая избавление от обобщений)?

Если ваши классы, такие как NumberGeneratorApple на самом деле есть только один метод, и это не просто упрощение более сложных классов с несколькими методами для целей этого вопроса, тогда вы можете использовать ссылки на методы, как подсказал другой ответ.Вместо объявления собственного интерфейса NumberGeneratorInterface, ссылка на метод может быть напечатана как Supplier<Integer>.

public class ClassThatOperates {
    Supplier<Integer> generator;

    public ClassThatOperates(Supplier<Integer> generator) {
        this.generator = generator;
    }

    public void doSomeOperation(){
        System.out.print("The number with constant is: ");
        System.out.println(generator.get() + 5);
    }
}

Затем вы можете использовать его как:

NumberGeneratorOrange ngo = new NumberGeneratorOrange();
ClassThatOperates cto = new ClassThatOperates(ngo::getNumber);
cto.doSomeOperation();
0 голосов
/ 28 ноября 2018

С помощью java8 вы можете создать один из ваших пользовательских классов

NumberGeneratorApple apple = new NumberGeneratorApple();

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

Supplier<Integer> numberGenerator = apple::getNumber;

, затем вы можете передать его или сделать все, что вам нужнос этим:

Integer number = numberGenerator.get();
externalObject.execute(numberGenerator);

Как это звучит?

0 голосов
/ 28 ноября 2018

Рассмотрим:

class ClassThatOperates
{
    final Supplier<Integer> m_generator;

    ClassThatOperates(Supplier<Integer> generator)
    {
        m_generator = generator;
    }
    void doSomething()
    {
        ...
        m_generator.get();
    }
}

NumberGeneratorApple nga = new NumberGeneratorApple();
ClassThatOperates cta = new ClassThatOperates(nga::generate);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...