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).
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.
"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.
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.