Реализация нескольких универсальных интерфейсов в Java

10

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

public interface Mappable<M> {
    M mapTo(M mappableEntity);
}

Проблема возникает, когда класс должен быть сопоставим с несколькими другими объектами. Идеальный случай был бы такой (не Java):

public class Something implements Mappable<A>, Mappable<B> {
    public A mapTo(A someObject) {...}
    public B mapTo(B someOtherObject) {...}
}

Каков был бы лучший способ достичь этого, оставаясь как можно более «общим»?

estani
источник

Ответы:

10

Это, конечно, не то, что вы можете сделать из-за стирания типа . Во время выполнения у вас есть два метода public Object mapTo(Object), которые явно не могут сосуществовать.

К сожалению, то, что вы пытаетесь сделать, просто выходит за рамки системы типов Java.

Предполагая, что ваш универсальный тип всегда является типом первого класса, а не сам по себе универсальный, вы могли бы добиться аналогичного внешнего поведения, имея метод mapTo(Object, Class), который позволил бы вам выполнять проверку заданного класса во время выполнения и решать, какое поведение использовать. Очевидно, что это довольно не элегантно - и потребует ручного приведения возвращаемого значения - но я думаю, что это лучшее, что вы можете сделать. Если ваши универсальные типы сами являются универсальными, то их универсальные параметры также будут удалены, и их классы будут равны, поэтому этот метод не будет работать.

Тем не менее, я бы также указал на ответ @ Joachim, это может быть случай, когда вы можете разделить поведение на отдельные компоненты и обойти весь вопрос.

Phoshi
источник
3

Как вы видели, вы не можете реализовать один и тот же интерфейс дважды с разными параметрами типа (из-за стирания: во время выполнения они являются одинаковыми интерфейсами).

Кроме того, этот подход нарушает принцип единственной ответственности: ваш класс должен сосредоточиться на том, чтобы быть Something(что бы это ни значило), и не должен выполнять сопоставление Aили B дополнение к этой задаче.

Похоже, вы действительно должны иметь Mapper<Something,A>и Mapper<Something,B>. Таким образом, каждый класс несет одну четко определенную ответственность, и вы не сталкиваетесь с проблемой реализации одного и того же интерфейса дважды.

Йоахим Зауэр
источник
Идея состоит в том, чтобы позволить классу отвечать за «преобразование» своего содержимого в другие объекты. Кроме того, есть диспетчер, который обрабатывает их независимо от класса, поэтому требуется универсальное требование. Я немного подумаю об извлечении логики, хотя на самом деле это означает, что я делю класс на две части, но они остаются тесно связанными (доступ к полю должен быть предоставлен, модификация одного в большинстве случаев подразумевает модификацию другого и т. Д.)
Эстани
@estani: да, они несколько тесно связаны, но у них разные обязанности. Также подумайте об этом: когда вы вводите новый класс Cи хотите, Somethingчтобы его можно было сопоставить, вам нужно будет изменить Something, что слишком сильно связывает. Просто добавление нового SoemthingToCMapperменее навязчиво.
Йоахим Зауэр
1
+1 к этому - вообще говоря, вы должны отдавать предпочтение композиции, а не наследованию, если вы пытаетесь достичь того, что хочет OP (в Java). Java 8 с методами по умолчанию делает это еще проще - но далеко не каждый может встать на передний край :-).
Мартейн Вербург
0

Учитывая, что нельзя использовать несколько интерфейсов, вы можете рассмотреть возможность использования инкапсуляции. (пример использования java8 +)

// Mappable.java
public interface Mappable<M> {
    M mapTo(M mappableEntity);
}

// TwoMappables.java
public interface TwoMappables {
    default Mappable<A> mapableA() {
         return new MappableA();
    }

    default Mappable<B> mapableB() {
         return new MappableB();
    }

    class MappableA implements Mappable<A> {}
    class MappableB implements Mappable<B> {}
}

// Something.java
public class Something implements TwoMappables {
    // ... business logic ...
    mapableA().mapTo(A);
    mapableB().mapTo(B);
}

Пожалуйста, проверьте здесь для получения дополнительной информации и больше примеров: Как создать класс Java, который реализует один интерфейс с двумя универсальными типами? ,

зима
источник
-1
public interface IMappable<S, T> {
    T MapFrom(S source);
}

// T - target
// S - source

Если вы хотите сопоставить User с UserDTO и сопоставить User с UserViewModel, вам потребуются две отдельные реализации. Не складывайте всю эту логику в один класс - это не имеет смысла.

Обновление, чтобы держать Иоахима счастливым

public interface ITypeConverter<TSource, TDestination>
{
    TDestination Convert(TSource source);
}

Но теперь мы находимся в сфере Automapper ( http://automapper.codeplex.com/wikipage?title=Custom%20Type%20Converters )

CodeART
источник
Я не думаю, что IMappableэто хорошее название для чего-то, что отображает другие вещи. Mapper(или IMapper, если нужно ;-)), вероятно, более правильно. (Кстати: нет, это не было моим отрицательным голосом).
Йоахим Зауэр
Я взял то, что было в вопросе и с префиксом I, чтобы подчеркнуть тот факт, что это интерфейс. Я решаю проблему дизайна в соответствии с вопросом, в отличие от проблемы именования.
CodeART
1
извините, но, на мой взгляд, никто не может действительно «решить» проблему дизайна и игнорировать наименования. Дизайн означает понятные структуры. Неправильное наименование является проблемой для понимания.
Йоахим Зауэр
Обновление должно положить ваш ум в покое ;-)
CodeART
1
@CodeART Если я правильно понял ваш ответ, это подразумевает, что «MapFrom» (который должен быть в нижнем регистре ;-) создает объект. В моем случае это просто заполнение информацией о уже созданном объекте.
Эстани