Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it bad practice to return a generic inside an abstract class of different generic parameter

I have this abstract class where I have defined some methods that implement database actions (fetch rows, insert, delete, etc.)

Now I want to make method that will return some rows (i.e. the whole table) but instead of the domain classes I want it to return the corresponding model classes (which basically is the same as domain but without the relationship lists and some other stuff I don't need for the presentation layer).

The abstract class is

public abstract class DomainService<T extends Domain> {

    protected abstract Logger getLogger();

    protected final Validator validator;

    protected DomainService() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        this.validator = factory.getValidator();
    }

    abstract public void insert(T object) throws ValidationException;

    abstract public void delete(T object) throws EntityNotFoundException;

    abstract public List<T> fetchAll();
}

and I want to add another method that will call fetchAll() and then iterate each item and create the model equivalent and return that list.

public <K extends  Model> List<K> fetchAllModels(Class<K> modelClass) {
        List<T> domains = fetchAll();
        List<K> models = new ArrayList<K>(domains.size());

        for ( T domain : domains) {
            K model = modelClass.newInstance();
            models.add(model.fillIn(domain));
        }

        return models;
    }

Disregarding that this is the code I though just now writing the question, is it acceptable to add a parameter for a generic that is not defined in the class. IMO a class can have methods returning other data types so it should not be a problem. In my case I pass the class so I can create an instance of the model and then use the domain to fill the members. I was of two opinions,

  • The one I wrote where I add a method to the model class to create it self from the domain object. I was thinking of a constructor that takes the domain object as an argument, but I think it's a bit of a hassle to call a constructor using generics (It would need reflection utilities at the very least) so I though of a method to fill the details after creating an instance using the default constructor. Also the model is on a higher layer and I think higher layers should use lower ones (Database->Domain classes->Access classes (DAO)->Service classes->Servlet classes----> JSP showing data)

  • I could add a method to the domain class that transforms the domain to its model and call that without having to pass the class of the model

    public <K> List<K> fetchAllModels() {
        List<T> domains = fetchAll();
        List<K> models = new ArrayList<K>(domains.size());
    
        for ( T domain : domains) {
            models.add(domain.createModel());
        }
    
        return models;
    }
    

but I feel that the domain class should be as clean a representation of the table in the database with the only methods having to do with the columns.

Would it better to add the parameter on the class. I am only going to use it for this method...

Any thoughts comments always welcome

like image 987
Andreas Andreou Avatar asked May 19 '15 14:05

Andreas Andreou


1 Answers

is it acceptable to add a parameter for a generic that is not defined in the class

Absolutely. It's done all the time.

I prefer your first solution, passing the model to the method.

But, what you really want there is a function that creates K from T. In java8, this can be done very succinctly.

public <K extends  Model> List<K> fetchAllModels(Function<T,K> func) {
...
            K model = func.apply(domain);

and say you have a Model 'M' for domain 'D'

public M(D domain) // constructor

you can pass the constructor as func (or at least it seems so)

    service.fectchAllModels( M::new )

If you use Stream, fetchAllModels() becomes much simpler

abstract public Stream<T> fetchAll();

public <K extends  Model> Stream<K> fetchAllModels(Function<T,K> func) {
    return fetchAll().map(func)
}

And then, why do we even need this method? Just do

// fetch domains, convert each to M
Stream<M> models = service.fetchAll().map( M::new );

So we can remove fetchAllModels(), and remove any dependencies on model from domain.

like image 181
ZhongYu Avatar answered Nov 01 '22 23:11

ZhongYu