Как использовать Java-интерфейсы с несколькими реализующими классами - PullRequest
3 голосов
/ 03 октября 2009
public interface Foo {
}

public class SpecificFoo implements Foo {
}

public interface SomeInterface {
    void thisMethod(Foo someKindOfFoo);
}

public class SomeClass implements SomeInterface {

    public void thisMethod(Foo someKindOfFoo) {
        // calling code goes into this function
         System.out.println("Dont go here please");
    }

    public void thisMethod(SpecificFoo specificFoo) {
        // not into this function
         System.out.println("Go here please");
    }
}

public class SomeOlderClass {

    public SomeOlderClass( SomeInterface inInterface ) {
        SpecificFoo myFoo = new SpecificFoo();

        inInterface.thisMethod(myFoo);
    }
}

телефонный код:

SomeClass myClass = new SomeClass();
SomeOlderClass olderClass = new SomeOlderClass(myClass);

У меня есть интерфейс (SomeInterface), к которому обращаются несколько классов (например, SomeOlderClass). У меня есть класс, который реализует интерфейс, но я хочу делать безопасные операции над типами в конкретных реализациях, которые передаются в общий интерфейс.

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

Так как я могу сделать это самым элегантным способом? Я могу заставить код работать, добавив оператор if в класс, реализующий интерфейс (SomeClass):

public void thisMethod(Foo someKindOfFoo) {
    // calling code goes into this function
    if ( someKindOfFoo.getClass().equals(SpecificFoo.class) )
        thisMethod(SpecificFoo.class.cast(someKindOfFoo));
    else
        System.out.println("Dont go here please");
}

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

Другой вариант - добавить SpecificFoo к SomeInterface и позволить компилятору разобраться, напомнив мне, что мне нужны реализации в SomeClass. Проблема с этим заключается в том, что я добавляю совсем немного кода для котельной плиты. (Если кто-то еще реализует интерфейс, он должен реализовать новый метод, а также любые тесты)

Кажется, должен быть другой вариант, который мне не хватает, учитывая, что Foo и SpecificFoo связаны между собой. Идеи?

ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ:

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

По сути, я пишу сервлет RPC веб-приложений GWT, используя шаблон команды, как объяснил Рэй Райан в своем выступлении

Существует несколько реализаций этого в коде Google, но многие из них страдают от этой проблемы наследования. Я думал, что это ошибка в коде GWT-RPC bugreport ОДНАКО, по мере реализации я заметил, что аналогичная проблема возникает исключительно на стороне клиента и в режиме хостинга. (т. е. все Java, без GWT Javascript безумие).

Итак, я абстрагировал основные идеи в случае командной строки исходного кода Java и увидел ту же проблему, как описано выше.

Если вы следуете тому, что обсуждает Рэй Райан, Foo - это Действие, SpecificFoo - это конкретное действие, которое я хочу вызвать. SomeInterface - это RPC-сервис на стороне клиента, а SomeClass - RPC-класс на стороне сервера. SomeOlderClass - это разновидность службы rpc, которая знает о кешировании и прочем.

Очевидно, верно? Ну, как я уже сказал, я думаю, что вся ерунда GWT RPC только запутывает воду в базовом вопросе, поэтому я попытался упростить ее настолько, насколько мог.

Ответы [ 4 ]

3 голосов
/ 03 октября 2009

Если вам нужно выяснить фактический тип объекта во время выполнения, то, скорее всего, проект ошибочен. Это нарушает как минимум Открытый Закрытый Принцип и Принцип Инверсии Зависимостей .

(Поскольку в Java нет многократной отправки , вместо thisMethod(SpecificFoo) будет вызываться thisMethod(Foo). Двойная отправка может использоваться для обхода ограничений языка, но там все еще может быть какая-то проблема с дизайном ...)

Пожалуйста, дайте больше информации о том, чего вы пытаетесь достичь. Прямо сейчас вопрос не дает достаточно информации, чтобы придумать правильный дизайн.

Общее решение состоит в том, что, поскольку действие зависит от типа среды выполнения Foo, этот метод должен быть частью Foo, поэтому его реализация может варьироваться в зависимости от типа Foo. Таким образом, ваш пример будет изменен на что-то вроде ниже (возможно добавление SomeInterface или других параметров к thisMethod()).

public interface Foo {
    void thisMethod();
}

public class SpecificFoo implements Foo {
        public void thisMethod() {
                 System.out.println("Go here please");
        }
}
2 голосов
/ 03 октября 2009

Извините, я нахожу описание проблемы слишком абстрактным, чтобы давать рекомендации. У вас явно есть проблема с дизайном, потому что вам обычно не нужно проверять тип интерфейса. Я попробую, хотя ... Во-первых, мне нужно сделать вашу проблему более конкретной, чтобы мой маленький мозг понял. А не Птицы, а Птицы?

public interface Bird {
}

public class Ostrich implements Bird {
}

public interface BirdManager {
    void fly(Bird bird);
}

public class AdvancedBirdManager implements BirdManager {

    public void fly(Bird bird) {
        System.out.println("I am in the air. Yay!");
    }

    public void fly(Ostrich ostrich) {
        System.out.println("Sigh... I can't fly.");
    }
}

public class ZooSimulation {
    public ZooSimulation(BirdManager birdManager) {
        Ostrich ostrich = new Ostrich();
        birdManager.fly(ostrich);
    }
}

public static void main(String[] args) {
    AdvancedBirdManager advancedBirdManager = new AdvancedBirdManager();
    ZooSimulation zooSimulation = new ZooSimulation(advancedBirdManager);
}

Здесь Страус объявит: «Я в воздухе. Ух!» что не то, что мы хотим.

ОК, поэтому, игнорируя тот факт, что у меня не получается выполнить базовый ОО, проблема в том, что BirdManager будет искать наименее специфичный метод, соответствующий типу, который передается. это всегда будет соответствовать fly(Bird). Мы можем поставить несколько проверок if, но по мере добавления новых типов птиц ваш дизайн будет ухудшаться. Вот сложная часть - я понятия не имею, имеет ли это смысл в контексте вашей проблемы, но рассмотрим этот рефакторинг, где я перенесу логику из менеджера в птицу:

public interface Bird {
    void fly();
}

public class BasicBird implements Bird {
    public void fly() {
        System.out.println("I am in the air. Yay!");
    }
}

public class Ostrich implements Bird {
    public void fly() {
        System.out.println("Sigh... I can't fly.");
    }
}

public interface BirdManager {
    void fly(Bird bird);
}

public class AdvancedBirdManager implements BirdManager {

    public void fly(Bird bird) {
        bird.fly();
    }
}

public class ZooSimulation {
    public ZooSimulation(BirdManager birdManager) {
        Ostrich ostrich = new Ostrich();
        birdManager.fly(ostrich);
    }
}

public static void main(String[] args) {
    AdvancedBirdManager advancedBirdManager = new AdvancedBirdManager();
    ZooSimulation zooSimulation = new ZooSimulation(advancedBirdManager);
}

Наш Страус теперь говорит правильные слова, и менеджер по-прежнему относится к ним как к птице. Опять же, плохой ОО (у Страусов не должно быть fly() методов), но это иллюстрирует мои мысли.

2 голосов
/ 03 октября 2009

Попробуйте использовать двойную диспетчеризацию: добавьте метод к интерфейсу Foo, который вызывается SomeClass#thisMethod. Затем поместите код в реализацию этого метода.

public interface Foo {
  public void thatMethod(SomeClass a);
  public void thatMethod(SomeOlderClass a);
}

public class SomeClass implements SomeInterface {
    public void thisMethod(Foo someKindOfFoo) {
        someKindOfFoo.thatMethod(this);
    }
}
0 голосов
/ 03 октября 2009

Пока существует не слишком много реализаций Foo, я бы объявил абстрактный метод в SomeInterface для каждого подкласса Foo, и у абстрактного класса были бы прямые вызовы метода по умолчанию, который определен для самый общий тип:

public interface Foo {
}

public class SpecificFoo implements Foo {
}

public interface SomeInterface {
        void thisMethod(Foo someKindOfFoo);
        void thisMethod(SpecificFoo specificFoo);
        void thisMethod(OtherSpecificFoo otherSpecificFoo);
}

public abstract class AbstractSomeInterface {
        public void thisMethod(Foo wrongFoo) {
            throw new IllegalArgumentException("Wrong kind of Foo!");
        }

        public void thisMethod(SpecificFoo specificFoo) {
            this.thisMethod((Foo) specificFoo);
        }

        public void thisMethod(OtherSpecificFoo otherSpecificFoo) {
            this.thisMethod((Foo) specificFoo);
        }
}

public class SomeClass extends AbstractSomeInterface {
        public void thisMethod(SpecificFoo specificFoo) {
                 // calling code goes into this function
                 System.out.println("Go here please");
        }
}

public class SomeOlderClass {

        public SomeOlderClass( SomeInterface inInterface ) {
                SpecificFoo myFoo = new SpecificFoo();

                inInterface.thisMethod(myFoo);
        }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...