Referencja do metody w klasie generycznej.

0

Witam,
Szukam pomocy, gdyż mam problem z przerobieniem klasy na generyczną, a dokładniej referencją do niej metody.
Jako dodatek, ale nie jest to dla mnie wymagane fajnie by było, gdyby była możliwość przypisania referencji do metody na podstawie klasy, czyli :
Podajemy jako parametr klasa1 i jest tutaj np refleksja(tak to się chyba nazywa) MetodaKlasa1Metoda.
Chyba, że całkowicie inaczej powinienem to zrobić, bo się inaczej do tego zabieram.

Poniżej kod, jak to wygląda, a problem leży w implementacji generyka w klasie GetQueryUserDetailService

public abstract class GetQueryAbstractService<B, R> {

    @PersistenceContext
    protected EntityManager entityManager;

    protected abstract Class<B> getClazz();

    protected abstract Predicate getPredicate(Predicate predicate, CriteriaBuilder builder, Root r, List<SearchCriteria> params);

    protected abstract List<R> getDtos(CriteriaQuery<B> query);

    public List<R> execute(String search) {
        final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        final CriteriaQuery<B> query = builder.createQuery(getClazz());
        final Root r = query.from(getClazz());

        List<SearchCriteria> params = new ArrayList<>();
        if (search != null) {
            Pattern pattern = Pattern.compile("(\\w+?)(:|<|>)(\\w+?),");
            Matcher matcher = pattern.matcher(search + ",");
            while (matcher.find()) {
                params.add(new SearchCriteria(matcher.group(1), matcher.group(2), matcher.group(3)));
            }
        }

        Predicate predicate = builder.conjunction();
        predicate = getPredicate(predicate, builder, r, params);
        query.where(predicate);

        return getDtos(query);
    }
}
@Service
public class GetQueryUserDetailService extends GetQueryAbstractService<UserDetail, UserDetail.UserDetailDto> {

    @Override
    protected Class<UserDetail> getClazz() {
        return UserDetail.class;
    }

    @Override
    protected Predicate getPredicate(Predicate predicate, CriteriaBuilder builder, Root r, List<SearchCriteria> params) {
        UserSearchQueryCriteriaConsumer searchConsumer = new UserSearchQueryCriteriaConsumer(predicate, builder, r);
        params.forEach(searchConsumer);
        return searchConsumer.getPredicate();
    }

    @Override
    protected List<UserDetail.UserDetailDto> getDtos(CriteriaQuery<UserDetail> query) {
        return entityManager
                .createQuery(query)
                .getResultStream()
                .map(UserDetail::toUserDetailDto)
                .collect(Collectors.toList());
    }
}

Mam problem z : map(UserDetail::toUserDetailDto) , chyba że to nie działa, bo źle podaję genęryki.
Oczywiście, żeby kod był sprawny, to nie implementowałem powyżej wartości generycznych, żeby nie zrobić burdelu w kodzie.

Za pomoc/ wskazówki z góry dziękuje.

0

Tak linijka będzie działa, pod warunkiem że w UserDetail masz funkcje:

public class UserDetail {
    public UserDetailDto toUserDetailDto() {

albo jeśli jest statyczna

public class UserDetail {
    public static UserDetailDto toUserDetailDto(UserDetail ) {

Jeśli nie, to nie będzie działać. Najlepiej pokaż kod UserDetail.

0

Nie wiem czy to nie moja godzina, czy inny powód, ale za nic nie potrafię zrozumieć tego co napisałeś 😶

0

@QuantumComp:

TomRiddle napisał(a):

Najlepiej pokaż kod UserDetail.

0

Kurde, może faktycznie mogłem to napisać po rusku ,postaram się to inaczej opisać.

@TomRiddle ten kod działa, wraz z mapowaniem, tylko ja chcę to przerobić na klasę generyczną, czyli w tym przypadku by było:
.map(T::R)

@QuantumComp up

0

@Nowak Adam: aaa. tak się nie da.

1

Musisz albo wydzielić interfejs, albo przekazać Function<R,U> jako parametr.

1
Nowak Adam napisał(a):

Byłbyś w stanie napisać jakiś pseudo kod, czy coś, bo nie wiem, czy rozumiem. Mam po prostu stworzyć osobną klasę generyczną, której zadanie będzie tylko przekazanie R,U jako parametr? A wtedy tamtą funkcją przerobić, żeby przyjmowała od niej ten parametr ?

Tak na prawdę to zależy od tego co chcesz zrobić. Czy te parametry które chcesz przekazać tymi generykami mają zawsze tą samą metodę? Np wszystkie mają mapToDto()? Czy mają różne? np mapToDto, mapToCount(), mapToTable(), mapToView(), etc.

3
Nowak Adam napisał(a):

Właśnie wszędzie jest ta sam schemat czyli np : Klasa1 a tam toKlasa1Dto Klasa2 a tam toKlasa2Dto etc

No to masz dwie opcje.

Albo zrobić nowy interfejs generyczny (interface Klasa<R> { R map() }) , że każda z Twoich klas Klasa1, Klasa2 go będzie implementowała (tzn Klasa1 implements Klasa, Klasa2 implements Klasa, etc.)

Albo, możesz przekazać parametr do swojej klasy abstrakcyjnej.

1

Można też trochę inaczej podejść do tego co napisał @TomRiddle, czyli zrobić sobie interfejs Mapper<FROM,TO> z metodą TO map(FROM obj) i taki interfejs przekazywać jako dodatkowy parametr do GetQueryAbstractService. Jeżeli być skorzystał z jakiejś biblioteki do mapowania opartej o refleksje (nie powiem że to polecam) to dało by radę obejść się i bez tego parametru.

Alternatywnie możesz dołożyć extra metodę abstrakcyjną która wykona wspomniane tutaj mapowanie typu:

abstract DTO map(ENTITY entityt);

Dla masochistów: da radę to również zrobić refleksją, gdzie na podstawie klasy encji i docelowego DTO budujesz interfejs Function<A,R> - ale będzie to wolne i podatne na błędy.

PS. Pattern warto skompilować raz na poziomie klasy. O ile dobrze pamiętam skompilowane Patterny są thread-safe...

0
public class User extends BaseEntity {

	@Override
	public UserDto toDto() {
		System.out.println(this);
		return new UserDto() {};
	}
}

public abstract class BaseService<E extends BaseEntity> {

	public BaseDto toDto(E entity) {
		return Stream.of(entity).map(BaseEntity::toDto).findFirst().get();
	}
}

dao.User@3af49f1c

0

@ćk: Burn it with fire. Po co dziedziczenie?

0

Co Ci w ogóle daje wprowadzenie takiej hierarchii klas? Jakiś własny framework piszesz?

Been there, done that. Kiedyś też robiłem coś podobnego i szczerze odradzam ten kierunek :) Zamiast tworzyć normalny soft zespół będzie rozwiązywał puzzle z generykami.

0

Generalnie takie generyki moga mieć sens conajwyżej w CRUDach do todo-list :P W prawdziwym programowaniu tego typu rzeczy nie mają racji bytu. Jeśli już niestety masz JPA to wywołuj EntityManager w danym DAO albo użyj Spring Data JPA. Takie generyczne rozwiązania sa słabe bo
1)Są mało czytelne
2)Tak czy owak musisz jakoś customizować pytania, np.robiąc fetch joiny
Generalnie dużo więcej problemów niż zysków.
No i nie ma czegoś takiego jak jeden "DTOs" odpowiadajacy jednej encji. Możesz mieć np. encję bazodanową Issue, ale możesz jakiś Issue i IssueWithComments

0

@scibi92: oki, myślałem, że tak będzie przejrzyściej, to w takim razie w jaki sposób powinienem ugryźć powyższy temat. Szukałem informacji, ale powyższy kod to wg mnie była najlepsza opcja.

0

Z encji wyciągasz dane getterami, przy czym należy uważać żeby były wywoływane w ramach transakcji. A operacje "niskopoziomowe" takie jak wczytanie listy encji przemyciłbym do jakiegoś DAO, ale nie generycznego. Encje zaś dalej mapuje sie na jakieś obiekty DTO/domenowe, ale nie powinno się robić założenia że jedna encja = jedno DTO.

0

@scibi92: Znaczy aktualnie 1 encja = 1 DTO to tylko dlatego, że po prostu nie potrzebuje więcej, bo mogę bez problemu dołożyć kolejne.
Napiszę od nowa ten post, żeby się dodatkowo poradzić.

0

Postarałem się wyjaśnić na nowo temat w nowym poście, proszę moderację o usunięcie, bądź zablokowanie, albo po prostu zostawienie, wg waszego uznania.
Za problemy przepraszam.

0

Daj spokój. Ile w twoim problemie pomogli "architekci"? Jakiś bełkot widzę.

0

Znaczy ja akurat nawet praw nie mam do oceniania, bo ja tu proszę o pomoc.
Napisałem post, bo nie do końca rozumiem po prostu odpowiedzi , zdaje sobię sprawę, że część to braki wiedzy, ale gdzieś tam może też być problem, że źle to wytłumaczyłem.

0
Nowak Adam napisał(a):

Znaczy ja akurat nawet praw nie mam do oceniania, bo ja tu proszę o pomoc.

Napisałem post, bo nie do końca rozumiem po prostu odpowiedzi , zdaje sobię sprawę, że część to braki wiedzy, ale gdzieś tam może też być problem, że źle to wytłumaczyłem.

Napisałem Ci, że masz dwie opcje do wyboru.

Musisz jakoś zmapować jeden typ na drugi, i gdzieś musi być logika do tego. Albo w implementacji pierwszego typu generycznego, albo dostarczona jako zewnętrzny, trzeci parametr.

Jedno jest specyfikacją implementacji w compile timie (interfejsy, dopisanie sygnatur), albo w runtime (przekazanie parametru).

0
cŻ napisał(a):

Podałeś bełkot, udowodnij kodem.

To się nazywa nomenklatura programistyczna. To że jest dla Ciebie niezrozumiała, nie znaczy że jest bełkotem.

To jest aktualny kod autora postu.

class Mapper<A, B> {
   public List<B> map(List<A> from) {
      return from.stream()
         .map(obiekt -> {
            return MagicznyMapper(from); // ofc nie będzie działać
         })
         .collect(toList());
   }
}

class First { }
class FirstDto { }
class Second { }
class SecondDto { }

Ma dwie opcje.

Albo dodać mapowanie w implementacji pierwszego typu generycznego (pierwsza część "bełkotu"):

var mapperFirst = new Mapper<First, FirstDto>();
var mapperSecond = new Mapper<Second, SecondDto>();

class Mapper<A extends Int<B>, B> { // A to pierwszy typ generyczny - interfejs
   public List<B> map(List<A> from) {
      return from.stream()
         .map(obiekt -> {
            return obiekt.map();
         })
         .collect(toList());
   }
}

class First implements Int<FirstDto> { public FirstDto map() { return new FirstDto(); }} // a to jego implementacja
class FirstDto { }

class Second implements Int<SecondDto> { public SecondDto map() { return new SecondDto(); }}
class SecondDto { }

interface Int<B> { 
   B map();
}

...w implementacji pierwszego typu generycznego jest mapowanie.

Albo, przekazać trzeci parametr i w ten sposób dostarczyć mapowanie

public class Application {
   public static void main(String[] args) {
      var mapperFirst = new Mapper<First, FirstDto>(first -> new FirstDto());  // parametr dostarczający implementację
      var mapperSecond = new Mapper<Second, SecondDto>(second -> new SecondDto());
   }
}

class Mapper<A, B> {
   private final Function<A, B> mapper;

   Mapper(Function<A, B> mapper) {
      this.mapper = mapper;
   }

   public List<B> map(List<A> from) {
      return from.stream()
         .map(obiekt -> {
            return mapper.apply(obiekt);
         })
         .collect(toList());
   }
}

class First { }
class FirstDto { }
class Second { }
class SecondDto { }

@ćk Najpierw poćwicz czytanie ze zrozumieniem, zanim zaczniesz hejtować.

0

@TomRiddle: ok :) ocenę pozostawię sobie

0
Nowak Adam napisał(a):

@TomRiddle: Nie do końca rozumiem twój przykład, mogę uderzyć na priv ?

Nie zostanie nic dla potomnych ;)

Żeby zrobić to co chcesz zrobić, najpierw musisz zdecydować czy info o mapowaniu usera na user dto, bardziej ma być bliżej usera, czy bardziej tej klasy abstrakcyjnej którą masz. Jeśli o tym zdecydujesz, to łatwo będzie wybrać czy interfejs czy parametr.

0

@TomRiddle:
No dobra, najwyżej ośmieszę się na gronie publicznym, jeśli rozwiąże to mój problem z tym zadaniem, to warto xd

Bo problem jeszcze nawet nie występuje na etapie mapowania, co samej implementacji przed mapowaniem(wspomniałem, że z tym też mam problem)

Czyli np podaję:

public class TempGetQueryUserDetailService<B,R extends TempGetQueryAbstractService<B, R>> {

ale z automatu mam wszędzie:

Method does not override method from its superclass

0

@Nowak Adam: A mógłbyś gdzieś podać więcej kodu? Wrzucić na githuba albo coś? Bo tak z taką linijką, to ciężko coś powiedziec.

0

@TomRiddle:
Mam problem już nie na poziomie samego mapowania Encja->DTo, a wywołanie samego Query do bazy.

GetQueryUserDetailService<UserDetail, UserDetailDto> - Nie chodzi o samą deklerację takiej klasy z tymi klasami, co utworzenie takiej samej klasy, która będzie przyjmowałą dwie wartości generycznE(Encja, oraz DTo Encji)
Czyli np: GetQueryUserDetailService<T, B>

Więc niestety nawet nie mam jak przetestować, twojego sposobu implementacji mapowania, bo upadłem na etapie tworzenia Query do bazy

0
Nowak Adam napisał(a):

@TomRiddle:

Mam problem już nie na poziomie samego mapowania Encja->DTo, a wywołanie samego Query do bazy.

GetQueryUserDetailService<UserDetail, UserDetailDto> - Nie chodzi o samą deklerację takiej klasy z tymi klasami, co utworzenie takiej samej klasy, która będzie przyjmowałą dwie wartości generycznE(Encja, oraz DTo Encji)
Czyli np: GetQueryUserDetailService<T, B>

Więc niestety nawet nie mam jak przetestować, twojego sposobu implementacji mapowania, bo upadłem na etapie tworzenia Query do bazy

W kwestii generyków to jest jeden i ten sam problem. Może po prostu opisz jak sobie wyobrażasz użycie tej klasy GetQueryUserDetailService.

0

@TomRiddle:

Hmm w sensie, że po prostu temat jest nie do ugryzienia z generykami ? To może przedstawię cały obraz sytuacji.

Aktualnie klasa wygląda tak:

public class GetQueryUserDetailService extends GetQueryAbstractService<UserDetail, UserDetail.UserDetailDto> {

Jest wywoływana w innym serwisie w ten sposób

private final GetQueryUserDetailService quer;
quer.execute(String search);

Jak widzimy klasa : GetQueryUserDetailService ma dwa argumenty - Jest to encja oraz DtoEncji

Natomiast potrzebuję zrobić tak, żeby klasa GetQueryUserDetailService przyjmowała jako argument dwie klasy - Encja oraz DtoEncji

I wtedy wywoływanie by było mniej więcej w innym serwisie:

private final GetQueryUserDetailService quer;
quer.execute(Encja, DtoEncja,String search);
0
Nowak Adam napisał(a):

Hmm w sensie, że po prostu temat jest nie do ugryzienia z generykami?

Mówię tylko, że z samych tylko generyków nie jesteś w stanie wykonać żadnej operacji w runtimeie.

Jest wywoływana w innym serwisie w ten sposób

private final GetQueryUserDetailService quer;
quer.execute(String search);

Jak widzimy klasa : GetQueryUserDetailService ma dwa argumenty - Jest to encja oraz DtoEncji

Nie widzę jak ona ma dwa "argumenty". Możesz masz na myśli dwa typy generyczne?

Natomiast potrzebuję zrobić tak, żeby klasa GetQueryUserDetailService przyjmowała jako argument dwie klasy - Encja oraz DtoEncji

I wtedy wywoływanie by było mniej więcej w innym serwisie:

private final GetQueryUserDetailService quer;
quer.execute(Encja, DtoEncja,String search);

No to nie, w ten sposób się tego nie da zrobić, bo nawet gdybyś wyciągnął refleksją tą klasę, to nie wiadomo by było jaką funkcję na niej wywołać (chyba że umówiłbyś się na jedną i tą samą nazwę, co w sumie jest tożsame z wydzieleniem interfejsu.

Nie da się zrobić tak:

private final GetQueryUserDetailService quer;
quer.execute(Encja.class, DtoEncja.class,String search);

Ale, da się zrobić tak:

private final GetQueryUserDetailService quer;
quer.execute(Encja.class, DtoEncja.class,String search, encja -> new DtoEncja(encja));
albo
quer.execute(Encja.class, DtoEncja.class,String search, DtoEncja::new);

Wtedy deklaracja powinna wyglądać tak

void execute(T encja, D dto, String search, Function<T, D> mapper)

1 użytkowników online, w tym zalogowanych: 0, gości: 1