Implementowanie wielu ogólnych interfejsów w Javie

10

Potrzebuję interfejsu, który zapewnia, że ​​pewna metoda, w tym konkretny podpis, jest dostępna. Do tej pory mam to:

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

Problem powstaje, gdy klasa powinna być odwzorowana na wiele innych encji. Idealnym przypadkiem byłoby to (nie java):

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

Jaki byłby najlepszy sposób, aby osiągnąć to jak najbardziej „ogólne”?

estani
źródło

Odpowiedzi:

10

Oczywiście nie jest to coś, co można zrobić z powodu usunięcia typu . W czasie wykonywania masz dwie metody public Object mapTo(Object), które oczywiście nie mogą współistnieć.

Niestety, to, co próbujesz zrobić, to po prostu system typu Java.

Zakładając, że Twój typ ogólny jest zawsze typem pierwszej klasy, a nie sam w sobie, możesz osiągnąć podobne zachowanie skierowane na zewnątrz, stosując metodę mapTo(Object, Class), która pozwoli ci przeprowadzić kontrolę środowiska wykonawczego danej klasy i zdecydować, którego zachowania użyć. Oczywiście jest to dość nieeleganckie - i będzie wymagało ręcznego rzutowania wartości zwracanej - ale myślę, że to najlepsze, co możesz zrobić. Jeśli typy ogólne są same w sobie rodzajowe, wówczas ich ogólne parametry również zostaną usunięte, a ich klasy będą równe, więc ta metoda nie będzie działać.

Chciałbym jednak zwrócić uwagę na odpowiedź @ Joachima, może to być przypadek, w którym można podzielić zachowanie na oddzielne komponenty i ominąć cały problem.

Phoshi
źródło
3

Jak widzieliście, nie można dwukrotnie zaimplementować tego samego interfejsu z różnymi parametrami typu (z powodu kasowania: w czasie wykonywania są to te same interfejsy).

Takie podejście narusza także zasadę pojedynczej odpowiedzialności: klasa powinna skupić się na byciu Something(cokolwiek to znaczy) i nie powinna wykonywać mapowania do tego zadania Alub B dodatkowo .

Wygląda na to, że naprawdę powinieneś mieć Mapper<Something,A>a Mapper<Something,B>. W ten sposób każda klasa ma jedną jasno określoną odpowiedzialność i nie napotkasz problemu podwójnego wdrożenia tego samego interfejsu.

Joachim Sauer
źródło
Chodzi o to, aby klasa była odpowiedzialna za „przekształcenie” jej zawartości na inne obiekty. Jest też dyspozytor, który obsługuje je w sposób agnostyczny klasy, dlatego też wymagania ogólne. Zastanowię się trochę nad wyodrębnieniem logiki, choć tak naprawdę oznacza to, że dzielę klasę na dwie części, ale pozostają one ściśle powiązane (dostęp do pola powinien być przyznany, modyfikowanie przez większość czasu oznacza modyfikację drugiej itp.)
estani
@estani: tak, są nieco ściśle powiązane, ale mają wyraźną odpowiedzialność. Pomyśl też o tym: kiedy wprowadzasz nową klasę Ci chcesz Somethingbyć do niej mapowalny, musisz zmodyfikować Something, co jest zbyt dużym sprzężeniem. Samo dodanie nowego SoemthingToCMapperjest mniej uciążliwe.
Joachim Sauer
1
+1 do tego - ogólnie rzecz biorąc, powinieneś preferować kompozycję zamiast dziedziczenia, jeśli próbujesz osiągnąć to, czego chce OP (w Javie). Java 8 z domyślnymi metodami sprawia, że ​​jest to jeszcze łatwiejsze - ale nie wszyscy mogą jeszcze skoczyć na najnowszą krawędź :-).
Martijn Verburg,
0

Biorąc pod uwagę, że nie wolno implementować interfejsów wielokrotnych, możesz rozważyć zastosowanie enkapsulacji. (przykład przy użyciu 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);
}

Sprawdź tutaj, aby uzyskać więcej informacji i więcej przykładów: Jak stworzyć klasę Java, która implementuje jeden interfejs z dwoma rodzajami ogólnymi? .

zimowy
źródło
-1
public interface IMappable<S, T> {
    T MapFrom(S source);
}

// T - target
// S - source

Jeśli chcesz zmapować użytkownika na UserDTO i zmapować użytkownika na UserViewModel, będziesz potrzebować dwóch osobnych implementacji. Nie łącz tej całej logiki w jedną klasę - nie ma sensu tego robić.

Zaktualizuj, aby uszczęśliwić Joachima

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

Ale teraz jesteśmy w sferze Automapper ( http://automapper.codeplex.com/wikipage?title=Custom%20Type%20Converters )

CodeART
źródło
Nie sądzę, że IMappableto dobre imię dla czegoś, co mapuje inne rzeczy. Mapper(lub IMapper, jeśli musisz ;-)) jest prawdopodobnie bardziej poprawny. (Nawiasem mówiąc: nie, to nie był mój głos).
Joachim Sauer
Wziąłem to, co było w pytaniu i przedrostek I, aby podkreślić fakt, że jest to interfejs. Rozwiązuję problem projektowy zgodnie z pytaniem, w przeciwieństwie do problemu nazewnictwa.
CodeART
1
przepraszam, ale moim zdaniem nie można tak naprawdę „rozwiązać” problemu projektowego i zignorować nazewnictwa. Projekt oznacza zrozumiałe struktury. Niepoprawne nazewnictwo jest problemem do zrozumienia.
Joachim Sauer
Aktualizacja powinna uspokoić ;-)
CodeART
1
@CodeART Jeśli poprawnie zrozumiałem twoją odpowiedź, oznacza to, że „MapFrom” (który powinien być pisany małymi literami ;-) tworzy obiekt. W moim przypadku to tylko wypełnianie informacji o już utworzonym obiekcie.
estani