Some of posts from this blog has been moved to dywicki.pl. You will be automatically redirected to new blog if you would submit comment.
New posts are published on dywicki.pl, this blog contains old content related to Code-House company.


Niektóre posty z tego bloga zostały przeniesione do dywicki.pl. Zostaniesz automatycznie przekierowany jeśli bedzięsz chciał dodać komentarz.
Nowe posty sa publikowane na dywicki.pl, ten blog zawiera stare treści związane z firmą Code-House.

Typy generyczne od teraz są mniej anonimowe

Do tej pory tworząc kod z użyciem typów generycznych zawsze borykałem się z problemem – jak dobrać się do klasy na podstawie któregoś parametru. Rozwiązanie znalazłem przeglądając w internecie kod jakieś przykładowej aplikacji. Ot znowu kilka linii które uporządkują kod i zmniejszą ilość powtórzeń. :-)

Dbanie o porządek w kodzie to obowiązek każdego programisty. Stąd podział aplikacji na warstwy, wyodrębnianie wspólnego kodu do typów abstrakcyjnych i tak dalej. Są jednak miejsca, których do tej pory nie potrafiłem uporządkować dobrze – były to wszelkiego rodzaju DAO, które zawsze wyglądały podobnie. Interfejs, klasa abstrakcyjna, pochodna wywołująca chroniony konstruktor. Wszystko po to by w klasie abstrakcyjnej móc zdefiniować metodę readById. Oto przykład takiego kodu:

// Ogólny interfejs dostępu do danych
// T to typ encji, K to typ klucza w danej encji.
public interface GenericDAO<T, K> {
    void save(T instance);
    void create(T newInstance);
    void update(T existingInstance);
    boolean delete(T instance);
    T readById(K id);
    List<T> getAll();
}
// Implementacja oparta o mechanizmy Springa
public abstract class AbstractDAO<T extends BaseEntity, K> extends
    JpaDaoSupport implements GenericDAO<T, K> {
    private Class<T> typeClass;
    protected AbstractDAO(Class typeClass) {
        this.typeClass = typeClass;
    }
    // pobranie obiektu na bazie id
    public T readById(K id) {
        return getJpaTemplate().find(typeClass, id);
    }
}

// Przekazanie User.class do AbstractDAO by te mogło 
// użyć go do odpytywania bazy danych
public class UserJpaDAO extends AbstractDAO<User, Long> implements UserDAO {
    public UserJpaDAO() {
        super(User.class);
    }
}

W tym wypadku typ generyczny zamiast uprościć życie nieco je komplikuje – w ostatniej klasie musimy pisać dwa razy User – raz jako parametr typu, dwa przekazując go do konstruktora klasy abstrakcyjnej. Logicznie rzecz biorąc. Znany jest typ generyczny zatem to nazwa klasy też powinna być do wykrycia. I tak rzeczywiście jest. Od JDK 1.5 istnieje interfejs ParameterizedType, który zawiera metodę getActualTypeArguments, Wystarczy dobrać się do tego obiektu i będzie z górki. Oto jak ja to rozwiązałem:

// Copyright (C) 2009 Code-House
// All rights reserved
package org.code_house.manager.dataaccess.jpa.util;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
 * Klasa pomocnicza do operowania na typach generycznych.
 *
 * @author Łukasz Dywicki luke@code-house.org
 *
 * $Id$
 */
public class GenericTypeUtil {

    /**
     * Konstruktor prywatny, na wypadek gdyby ktoś chciał stworzyć instancję
     * utila. :)
     */
    private GenericTypeUtil() {}

    /**
     * Pobranie argumentów dla danego typu na bazie klasy.
     */
    public static Class[] getGenericTypes(Class type) {
        Type superclass = type.getGenericSuperclass();
        if (superclass instanceof ParameterizedType) {
            ParameterizedType genericTypeClass = (ParameterizedType) superclass;
            return cast(genericTypeClass.getActualTypeArguments());
        }
        return new Class[0];
    }

    // Rzutowanie w dół z Type do Class.
    public static Class[] cast(Type[] types) {
        Class[] classTypes = new Class[types.length];
        for (int i = 0, j = types.length; i < j; i++) {
            classTypes[i] = (Class) types[i];
        }
        return classTypes;
    }
}

Co zwróci nam wywołanie GenericTypeUtil.getGenericTypes(UserJpaDAO.class)? To czego potrzebujemy! :)

[class org.code_house.manager.domain.User, class java.lang.Long]

Czy znaliście ten sposób? A może tylko ja byłem nieświadom tego rozwiązania?

 

5 replies


  1. Tutaj jest fajnie omówiony temat generycznych DAO:
    https://www.hibernate.org/328.html


  2. A czy wiesz może jak pobrać typy generyczny dla konkretnej nadklasy?

    Przykład:

    class A {}
    class B extends A {}
    class C extends B
    

    getTypes(C.class, A.class) – powinno zwrócić [MyObject]


  3. Cześć yew!

    Na pytanie, które zadałeś nie ma dobrej odpowiedzi. Jedyne co w tym wypadku można uzyskać to informacje o nazwie aliasu generyka (np. T, K) i jego granice w określonej hierarchii.

    W tym przypadku Reflection API zwróci java.lang.reflect.TypeVariable zamiast java.lang.Class.

    class GenericSecondExt<T> extends GenericExt<T, CharSequence> {}
    

    Lecąc w kolejności GenericSecondExt.class.getGenericSuperclass() zwróci TypeVariable z granicami [Object.class, Object.class] dla T a następnie Class dla CharSequence.


  4. Nie znałem, a właśnie tego “ogniwa” mi brakowało :) Dzięki za publikację.

    A czy mógłbyś się pokusić o test wydajnościowy? Tzn. zrobić porównanie prędkości pracy obu DAO, wywalając logikę biznesową naturalnie. Refleksje są dość kosztowne, to wiemy, ale w tym wypadku może nie jest tak źle. Z drugiej strony, jeśli narzut jest zbyt duży, to pewnie znajdą się osoby, które wolą skorzystać z rozwiązania pierwszego.
    Oczywiście jeśli narzut w ogóle jest dziesiątki razy mniejszy od późniejszego czasu dostępu do bazy, to lepiej być wygodnym i użyć rozwiązania drugiego.


  5. Zdanek:

    “Refleksje są dość kosztowne, to wiemy, ale w tym wypadku może nie jest tak źle. Z drugiej strony, jeśli narzut jest zbyt duży, to pewnie znajdą się osoby, które wolą skorzystać z rozwiązania pierwszego.”

    Tak, lecz popatrz, że DAO raczej powinieneś tworzyć przy starcie aplikacji, więc jedyne co będzie wolniejsze to start a nie samo działanie.

Leave a reply