Реалізація декількох загальних інтерфейсів у 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) {...}
}

Який був би найкращий спосіб досягти цього, залишаючись максимально «загальним»?

Відповіді:


10

Це, звичайно, не те, що ви можете зробити завдяки типу стирання . Під час виконання у вас є два методи public Object mapTo(Object), які, очевидно, не можуть співіснувати.

На жаль, те, що ви намагаєтеся зробити, просто поза системою типу Java.

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

Однак я б наголосив також на відповіді @ Йоахіма. Це може бути випадок, коли ви можете розділити поведінку на окремі компоненти та пройти повну проблему.


3

Як ви бачили, ви не можете реалізувати один і той же інтерфейс двічі з різними параметрами типу (через стирання: під час виконання вони є однаковими інтерфейсами).

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

Це здається, що ви справді повинні мати Mapper<Something,A>та і Mapper<Something,B>. Таким чином, кожен клас несе єдину чітко визначену відповідальність, і ви не стикаєтеся з проблемою впровадження одного і того ж інтерфейсу двічі.


Ну, ідея полягає в тому, щоб клас відповідав за "перетворення" його вмісту на інші об'єкти. Потім є диспетчер, який обробляє їх агностично, тому вимога generics. Я подумаю трохи про вилучення логіки, хоча це насправді означає, що я розбиваю клас на два, але вони залишаються щільно сполученими (доступ до поля повинен бути наданий, змінюючи один більшість разів, маючи на увазі модифікацію іншого тощо)
Естані

@estani: так, вони дещо щільно з'єднані, але мають чіткі обов'язки. Також подумайте над цим: коли ви вводите новий клас Cі хочете, Somethingщоб його можна було відобразити, то вам потрібно буде змінити Something, що є занадто сильним зв'язком. Просто додавання нового SoemthingToCMapperменш нав'язливе.
Йоахім Зауер

1
+1 до цього - загалом кажучи, ви повинні віддавати перевагу складу над спадщиною, якщо ви намагаєтесь досягти того, чого хоче ОП (на Java). Java 8 з методами за замовчуванням робить це ще простіше - але далеко не кожен може стрибати на межі кровотечі :-).
Martijn Verburg

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

Якщо ви хочете зіставити користувача на UserDTO та вказати користувача на UserViewModel, вам знадобляться дві окремі реалізації. Не збирайте всю цю логіку в один клас - це не має сенсу робити.

Оновлення, щоб радіти Йоахіму

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

Але зараз ми перебуваємо у царині Automapper ( http://automapper.codeplex.com/wikipage?title=Custom%20Type%20Converters )


Я не думаю IMappable, що це добре ім'я для чогось, що відображає інші речі. Mapper(або IMapper, якщо потрібно ;-)), мабуть, правильніше. (До речі: ні, це не було моїм свідченням).
Йоахім Зауер

Я взяв те, що було в питанні, і встановив префікс I, щоб виділити той факт, що це інтерфейс. Я вирішую задачу дизайну відповідно до запитання, на відміну від проблеми з називанням.
CodeART

1
вибачте, але, на мою думку, не можна реально "вирішити" проблему дизайну та ігнорувати найменування. Дизайн означає зрозумілі структури. Неправильне називання є проблемою для розуміння.
Йоахім Зауер

Оновлення повинно
переповнити

1
@CodeART Якщо я правильно зрозумів вашу відповідь, це означає, що "MapFrom" (який повинен бути нижчим регістром ;-) створює об'єкт. У моєму випадку це просто заповнення інформації про вже створений об’єкт.
Естані
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.