Общий вопрос Java: Есть ли лучший способ? - PullRequest
1 голос
/ 29 апреля 2009

[Обновление]: мой первоначальный пример не отражает мою проблему. Обновил образец, надеюсь, теперь лучше.

Java Generic и перегрузка плохо сочетаются друг с другом.

public interface Request<E> {
  E getValue();
}

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

Что я действительно хочу, так это что-то вроде

public interface DataStore {
  void save(Request<E1> r);
  void save(Request<E2> r);
}

Но это, конечно, незаконно.

Если я хочу сохранить безопасность типов, DataStore должен объявлять разные методы для каждого типа, с которым я хочу работать, например,

public interface DataStore {
  // here E1 is the actual type I want to handle, not a generic parameter
  public void saveE1(Request<E1> e); 

  public void saveE2(Request<E2> e);
}

Или жертвы типа безопасности

public class DataStore {
  public void save(Request e) {
    Object value = e.getValue();
    if (value instanceof ReqE1Impl) {
      // do something
    } else if (value instanceof ReqE2Impl) {
      // do something else
    }
  }
}

Оба способа отстой! Есть ли лучший способ?

Ответы [ 4 ]

4 голосов
/ 29 апреля 2009

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

interface YourInterface {
  void save();
}

interface Request<E extends YourInterface> { 
  E getValue(); 
} 

class DataStore<E extends YourInterface> {
  public void save(Request<E> r) {
    r.getValue().save();
  }
}

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

ПРИМЕЧАНИЕ. При написании вашего (исправленного) вопроса вы не используете полиморфизм. Вы используете перегрузку. Перегрузка метода определяется во время компиляции, тогда как полиморфизм метода определяется во время выполнения. Это часть того, почему вы сталкиваетесь с этими трудностями.

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

interface YourInterface {
  String serialize();
}

interface Request<E extends YourInterface> { 
  E getValue(); 
} 

class DataStore<E extends YourInterface> {
  public void save(Request<E> r) {
    String value = r.getValue().serialize();
    // Now do something with value to save to a datastore
  }
}

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

И да, вы правы (с неверной терминологией) - генерики не очень хорошо справляются с перегрузкой. Но они хорошо играют с полиморфизмом. Полиморфизм обычно лучший инструмент, чем перегрузка в любом случае.

3 голосов
/ 29 апреля 2009

Возможно, я неправильно понимаю вопрос, но дженерики, кажется, решают эту проблему нормально:

public interface Handler<E> {
    void save (E obj, DataStore<E> store);
}

public interface DataStore<E> {
    public void save (E obj);
}

public class AHandler implements Handler<String> {
    public void save (String obj, DataStore<String> obj2) {

    }
}

public class BHandler implements Handler<Boolean> {
    public void save (Boolean b1, DataStore<Boolean> obj3) {

    }
}
2 голосов
/ 29 апреля 2009

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

Не зная специфики типа E, я могу только сделать следующие общие предложения:

  • Возможно, DataStore должен иметь дело только с определенными подтипами E? Вы можете параметризовать интерфейс DataStore для обеспечения безопасности типов таким образом, то есть каждый Handler<E> принимает DataStore<E>.

  • В качестве альтернативы рассмотрите возможность переноса функции сохранения в реализацию вашего типа E - таким образом, DataStore не нужно заботиться о точном типе E.

1 голос
/ 29 апреля 2009

Не думаю, что проблема в генериках, а скорее в дизайне.

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

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

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

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