(J'ai vu cette question , mais la première réponse concerne plus les propriétés automatiques que la conception, et la seconde dit cacher le code de stockage des données au consommateur , ce que je ne suis pas sûr de savoir ce que je veux / mon code fait, donc j'aimerais entendre une autre opinion)
J'ai deux entités très similaires HolidayDiscount
et RentalDiscount
qui représentent des remises de durée comme «si cela dure au moins numberOfDays
, une percent
remise est applicable». Les tables ont des fks vers différentes entités parentes et sont utilisées à différents endroits, mais là où elles sont utilisées, il existe une logique commune pour obtenir la remise maximale applicable. Par exemple, a HolidayOffer
a un certain nombre de HolidayDiscounts
, et lors du calcul de son coût, nous devons déterminer la remise applicable. Idem pour les locations et RentalDiscounts
.
Puisque la logique est la même, je veux la garder en un seul endroit. C'est ce que font la méthode, le prédicat et le comparateur suivants:
Optional<LengthDiscount> getMaxApplicableLengthDiscount(List<LengthDiscount> discounts, int daysToStay) {
if (discounts.isEmpty()) {
return Optional.empty();
}
return discounts.stream()
.filter(new DiscountIsApplicablePredicate(daysToStay))
.max(new DiscountMinDaysComparator());
}
public class DiscountIsApplicablePredicate implements Predicate<LengthDiscount> {
private final long daysToStay;
public DiscountIsApplicablePredicate(long daysToStay) {
this.daysToStay = daysToStay;
}
@Override
public boolean test(LengthDiscount discount) {
return daysToStay >= discount.getNumberOfDays();
}
}
public class DiscountMinDaysComparator implements Comparator<LengthDiscount> {
@Override
public int compare(LengthDiscount d1, LengthDiscount d2) {
return d1.getNumberOfDays().compareTo(d2.getNumberOfDays());
}
}
Étant donné que les seules informations nécessaires sont le nombre de jours, je me retrouve avec une interface
public interface LengthDiscount {
Integer getNumberOfDays();
}
Et les deux entités
@Entity
@Table(name = "holidayDiscounts")
@Setter
public class HolidayDiscount implements LengthDiscount {
private BigInteger percent;
private Integer numberOfDays;
public BigInteger getPercent() {
return percent;
}
@Override
public Integer getNumberOfDays() {
return numberOfDays;
}
}
@Entity
@Table(name = "rentalDiscounts")
@Setter
public class RentalDiscount implements LengthDiscount {
private BigInteger percent;
private Integer numberOfDays;
public BigInteger getPercent() {
return percent;
}
@Override
public Integer getNumberOfDays() {
return numberOfDays;
}
}
L'interface a une seule méthode getter que les deux entités implémentent, ce qui bien sûr fonctionne, mais je doute que ce soit une bonne conception. Il ne représente aucun comportement, étant donné que la détention d'une valeur n'est pas une propriété. Ceci est un cas assez simple, j'ai quelques cas plus similaires, plus complexes (avec 3-4 getters).
Ma question est, est-ce une mauvaise conception? Quelle est la meilleure approche?
la source
Réponses:
Je dirais que votre conception est un peu erronée.
Une solution potentielle serait de créer une interface appelée
IDiscountCalculator
.Désormais, toute classe qui doit fournir une remise implémenterait cette interface. Si la remise utilise le nombre de jours, que ce soit, l'interface ne se soucie pas vraiment car ce sont les détails de la mise en œuvre.
Le nombre de jours appartient probablement à une sorte de classe de base abstraite si toutes les classes de remise ont besoin de ce membre de données. S'il s'agit d'une classe spécifique, il vous suffit de déclarer une propriété accessible en public ou en privé selon les besoins.
la source
HolidayDiscount
et laRentalDiscount
mise en œuvreIDiscountCalculator
, car ce ne sont pas des calculateurs de remise. Le calcul se fait dans cette autre méthodegetMaxApplicableLengthDiscount
.HolidayDiscount
etRentalDiscount
sont des remises. Un rabais n'est pas calculé, c'est un type de rabais applicable qui est calculé, ou simplement sélectionné parmi un certain nombre de rabais.Pour répondre à votre question: vous ne pouvez pas conclure une odeur de code si une interface n'a que des getters.
Un exemple de métrique floue pour les odeurs de code sont les interfaces gonflées avec de nombreuses méthodes (pas si les getters ou non). Ils ont tendance à violer le principe de séparation des interfaces.
Sur le plan sémantique, le principe de responsabilité unique ne devrait pas être également violé. J'ai tendance à être violé si vous voyez plusieurs problèmes différents traités dans le contrat d'interface qui ne sont pas un sujet. C'est parfois difficile à identifier et vous avez besoin d'une certaine expérience pour l'identifier.
De l'autre côté, vous devez savoir si votre interface définit des setters. C'est parce que les colons sont susceptibles de changer d'état, c'est leur travail. La question à poser ici: l'objet derrière l'interface fait-il une transition d'un état valide à un autre état valide. Mais ce n'est pas principalement un problème d'interface mais la mise en œuvre du contrat d'interface de l'objet d'implémentation.
La seule préoccupation que j'ai avec votre approche est que vous opérez sur OR-Mappings. Dans ce petit cas, ce n'est pas un problème. Techniquement, c'est ok. Mais je n'opérerais pas sur des objets OR-Mappés. Ils peuvent avoir trop d'états différents que vous ne tenez sûrement pas compte dans les opérations effectuées sur eux ...
Les objets mappés par OR peuvent être (présupposent JPA) transitoires, persistants détachés inchangés, persistants attachés inchangés, persistants détachés modifiés, persistants attachés modifiés ... si vous utilisez la validation de bean sur ces objets, vous ne pouvez plus voir si l'objet a été vérifié ou non. Après tout, vous pouvez identifier plus de 10 états différents que peut avoir l'objet OR-Mapped. Toutes vos opérations gèrent-elles correctement ces états?
Ce n'est certainement pas un problème si votre interface définit le contrat et que l'implémentation le suit. Mais pour les objets mappés OR, j'ai un doute raisonnable que le bidon peut remplir un tel contrat.
la source