web-dev-qa-db-fra.com

Ecrire une méthode avec la même logique mais traite différents objets

Supposons que j'ai la méthode suivante qui fait la même logique mais traite différents objets (c'est plus un pseudocode):


private <E> List<E> updateOrInsert(List<E> list) {

        if (list == null && list.size() == 0) {
            return list;
        }
        if (list.get(0) instanceof Explore) {
            List<String> numbers = new ArrayList<>();
            List<Explore> explorisList = (List<Explore>) list;
            explorisList.forEach(item -> {numbers.add(item.getNumber());});
            List<Explore> exploreDB = explorisRepository.findAllByNumberIn(numbers);

            Map<String, Explore> map = new HashMap<>();
            exploreDB.forEach(item -> {
                map.put(item.getNumber(), item);
            });

            explorisList.forEach(item -> {
                    Explore itemMap = map.get(item.getNumber());
                    if (itemMap != null) {
                        item.setId(itemMap.getId());
                }
            });

            return (List<E>) explorisRepository.saveAll(explorisList);

        } else if (list.get(0) instanceof Capital) {
            List<String> codes = new ArrayList<>();
            List<Capital> listOfCompanies = (List<Capital>) list;
            listOfCompanies.forEach(item -> codes.add(item.getCode()));
            List<Capital> capitalDB = companyRepository.findAllByCodeIn(codes);

            Map<String, Capital> map = new HashMap<>();
            capitalDB.forEach(item -> {
                map.put(item.getCode(), item);
            });

            ((List<Capital>) list).forEach(item -> {
                Capital itemMap = map.get(item.getCode());
                if (itemMap != null) {
                    item.setId(itemMap.getId());
                }
            });
            return (List<E>) companyRepository.saveAll(capitalSet);
//if statement continues but you got the picture
        } else {
            throw new UnsupportedOperationException("Unsupported operation for " + list.getClass().getName());
        }

    // ... etc.

Comme vous pouvez le voir, chaque liste a la même logique métier mais elle traite des objets différents.

Vaut-il mieux les séparer selon différentes méthodes? Et pourquoi?

Mon argument pour une telle approche est que je suppose qu'une fonction/méthode ne devrait représenter qu'une seule partie de la logique. Donc, créer des fonctions différentes qui font la même logique est une mauvaise conception.

Qu'en est-il de la maintenabilité?

J'ai également reçu un commentaire indiquant que j'utilise Java Generics de manière incorrecte et que j'ai affaire à Java comme langage fonctionnel).

6
hb0009

Il me semble que la racine de votre problème est le fait que Capital et Explore ne fournissent pas un moyen uniforme pour votre méthode updateOrInsert de pouvoir identifier votre Objets Explore et Capital.

J'envisagerais de généraliser cela et de mettre ces méthodes dans un interface que Explore et Capital sont capables de mettre en œuvre. Par exemple, au lieu de getCode et getNumber, il pourrait être généralisé comme getIdentifier à la place:

interface IEntity {
    String getIdentifier();
    void setId(int id);
    int getId();
}

Chacune des classes que vous devez prendre en charge dans ce modèle peut ressembler un peu à ceci, pour vous assurer que vous pouvez travailler avec elles à l'aide de l'interface généralisée à la place:

class Capital implements IEntity {
    @Override public void setId(int id) { _id = id; }
    @Override public int getId() { return _id; }

    @Override public String getIdentifier() { return getCode(); }
    public string getCode() { return _code; }
}

class Explore implements IEntity {
    @Override public void setId(int id) { _id = id; }
    @Override public int getId() { return _id; }

    @Override public String getIdentifier() { return getNumber(); }
    public string getNumber() { return _number; }
}

Cela vous offre un moyen uniforme pour la méthode updateOrInsert de travailler avec chacun des objets individuels.

Le problème suivant concerne les référentiels.

Étant donné le lien étroit entre votre méthode updateOrInsert et vos référentiels, je vous recommande de placer cette méthode dans une classe abstract que chacun des référentiels peut étendre, ce qui inclut également des signatures pour le find et save:

Par exemple:

abstract class RepositoryBase<T extends IEntity> {
    public abstract List<T> findAllByIdentifierIn(List<String> list);
    public abstract List<T> saveAll(List<T> list);

    public List<T> updateOrInsert(List<T> list) {
        /* TODO: Implementation here */
    }
}

En mettant une contrainte à l'aide de <T extends IEntity>, La méthode updateOrInsert pourra utiliser IEntity.setId, IEntity.getId Et IEntity.getIdentifier

Cette approche permet aux référentiels pour Explore et Capital d'étendre BaseRepository, puis de profiter de la logique de classe de base commune pour updateOrInsert:

class ExplorisRepository extends RepositoryBase<Explore> {
    @Override public List<Explore> findAllByIdentifierIn(List<String> list) {}
    @Override public void saveAll(List<Explore> list) {}
}

class CapitalRepository extends RepositoryBase<Capital> {
    @Override public List<Capital> findAllByIdentifierIn(List<String> list) {}
    @Override public void saveAll(List<Capital> list) {}
}

Ces classes de référentiel étendent RepositoryBase en fournissant le type réel de Capital ou Explore. Cela signifie que updateOrInsert n'a plus besoin de se préoccuper de différents types car la classe Repository transmettra ces informations à la base. Il connaît toujours le type exact avec lequel il travaille, et il n'est pas nécessaire d'effectuer une conversion ou une vérification de type car <T> Sera fortement tapé comme Explore ou Capital ou l'un des autres entités:

    // implementation of RepositoryBase<T>.updateOrInsert()
    public List<T> updateOrInsert(List<T> list){
        if (list == null || list.size() == 0) return list;

        List<String> identifiers = new ArrayList<>();
        list.forEach(item -> {identifiers.add(item.getIdentifier());});

        List<T> itemsFromDb = this.findAllByIdentifierIn(identifiers);

        Map<String, T> map = new HashMap<>();
        itemsFromDb.forEach(item -> {
            map.put(item.getIdentifier(), item);
        });

        list.forEach(item -> {
            T itemMap = map.get(item.getIdentifier());
            if (itemMap != null) {
                item.setId(itemMap.getId());
            }
        });

        return saveAll(list);
    }

Cela fonctionnera pour toutes les classes qui implémentent IEntity - cela peut fonctionner avec la méthode généralisée IEntity.getIdentifier Au lieu d'avoir à se soucier des spécificités entourant getNumber vs getCode (les classes concrètes s'occupent elles-mêmes de ce problème).

Il n'est pas nécessaire de dupliquer/copier-coller la logique de updateOrInsert car elle est déjà héritée par chacun des référentiels.

6
Ben Cottrell

"J'ai un commentaire que j'utilise Java Generics d'une manière incorrecte"

C'est aussi mon impression. Si vous mettez des éléments dans une liste générique et que vous travaillez avec cette liste, vous ne devriez généralement pas avoir à vous soucier du type des éléments ou à vous en préoccuper. Vous examinez des éléments et agissez en fonction de leur type. Cela va à l'encontre de l'objectif d'utiliser des génériques en premier lieu.

Je pense que vous devriez diviser les choses et utiliser des types d'éléments explicites plutôt que des génériques. Ensuite, demandez-vous ce qu'Explore et Capital ont vraiment en commun, le cas échéant. Vous pouvez être mieux avec un type de base commun pour Explore et Capital et utiliser le polymorphisme (utilisez une liste d'éléments de type de base et travaillez avec cela). Mais s'il n'y a pas de base commune, il vaut mieux avoir un code qui ressemble à celui d'essayer de le forcer en une seule chose alors que vous n'avez vraiment rien à faire.

2
Martin Maat

Il existe de nombreuses raisons pour lesquelles vous ne devriez pas faire de cette façon. J'essaie d'expliquer ce qui ne va pas et que devrez-vous faire également si vous faites votre chemin:

1. SEC

Lorsque vous faites cela, c'est contre de ne pas se répéter (SEC). Vous copiez et collez le même code pour chaque type. Par une bonne conception, vous pouvez le faire avec une seule méthode qui n'a pas besoin de connaître le type de données et il n'y aura qu'un seul endroit à changer en cas de besoin.

2. Limitation par type

Une autre raison pour laquelle vous ne devriez pas procéder de cette façon, vous limitez votre code d'une logique par type. Supposons que vous devez obtenir tous les Capital. Lorsque vous vérifiez le type d'objet dans votre code et faites votre logique, vous ne pouvez pas donner la flexibilité d'appeler un autre service.

public interface ICapitalService
{
    List<Capital> GetAll();
}

public class LocalCapitalService : ICapitalService
{
    public List<Capital> GetAll()
    {
        // get from db.
    }
}

public class RemoteCapitalService : ICapitalService
{
    public List<Capital> GetAll()
    {
        // get from web service
    }
}

Quelqu'un obtient des capitaux à partir d'un service distant et d'autres peuvent vouloir obtenir de db. Mais vous le limitez en vérifiant le type de Capital et en faisant votre logique spécifique dans votre code. Vous ne pouvez pas ajouter une autre option pour Capital.

. Vous devez vous assurer que seul le type de classe enfant vérifie.

Lorsque vous vérifiez le type par instanceof, il renvoie également true pour la classe parent. Ainsi, vous ne pouvez pas atteindre le bloc de code mérité.

if (list.get(0) instanceof Car) {
   //...
}
else if (list.get(0) instanceof Volvo)
{
  //..
}

Parce que Volvo est une voiture, vous ne pouvez pas atteindre le bloc Volvo et vous devez gérer manuellement.

4. Limitation de la langue du logiciel

Vous vérifiez la liste des types d'élément en obtenant le premier élément de la liste. (list.get(0)). Si le premier élément de la liste est nul, instanceof renverra false et la logique ne fonctionnera pas comme prévu et le dernier bloc else fonctionnera.

0
Engineert