Un'interfaccia con solo getter ha un odore di codice?


10

(Ho visto questa domanda , ma la prima risposta riguarda le proprietà automatiche più che la progettazione, e la seconda dice di nascondere il codice di archiviazione dei dati al consumatore , che non sono sicuro di ciò che voglio / il mio codice fa, quindi mi piacerebbe sentire qualche altra opinione)

Ho due entità molto simili HolidayDiscounte RentalDiscount, che rappresentano gli sconti di lunghezza come "se dura almeno numberOfDays, percentè applicabile uno sconto". Le tabelle hanno fk per diverse entità padre e vengono utilizzate in luoghi diversi, ma dove vengono utilizzate, esiste una logica comune per ottenere lo sconto massimo applicabile. Ad esempio, a HolidayOfferha un numero di HolidayDiscountse, nel calcolare il suo costo, dobbiamo capire lo sconto applicabile. Lo stesso vale per i noleggi e RentalDiscounts.

Poiché la logica è la stessa, voglio mantenerla in un unico posto. Ecco cosa fanno il seguente metodo, predicato e comparatore:

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());
    }
}

Poiché le uniche informazioni necessarie sono il numero di giorni, finisco con un'interfaccia come

public interface LengthDiscount {

    Integer getNumberOfDays();
}

E le due entità

@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'interfaccia ha un unico metodo getter implementato dalle due entità, che ovviamente funziona, ma dubito che sia un buon design. Non rappresenta alcun comportamento, dato che detenere un valore non è una proprietà. Questo è un caso piuttosto semplice, ne ho un paio in più di casi simili e più complessi (con 3-4 getter).

La mia domanda è: è un cattivo design? Qual è un approccio migliore?


4
Lo scopo di un'interfaccia è stabilire un modello di connessione, non rappresentare il comportamento. Tale dovere ricade sui metodi di attuazione.
Robert Harvey,

Per quanto riguarda l'implementazione, c'è un sacco di boilerplate (codice che in realtà non fa nulla, a parte fornire struttura). Sei sicuro di aver bisogno di tutto questo?
Robert Harvey,

I metodi di interfaccia Anche sono solo 'getter' non significa che non è possibile utensileria 'setter' relative all'attuazione (se tutti hanno setter, s è comunque possibile utilizzare un abstract)
рüффп

Risposte:


5

Direi che il tuo design è un po 'fuorviante.

Una potenziale soluzione sarebbe quella di creare un'interfaccia chiamata IDiscountCalculator.

decimal CalculateDiscount();

Ora qualsiasi classe che deve fornire uno sconto implementerebbe questa interfaccia. Se lo sconto utilizza il numero di giorni, così sia, l'interfaccia non si preoccupa davvero in quanto sono i dettagli di implementazione.

Il numero di giorni probabilmente appartiene a una sorta di classe base astratta se tutte le classi di sconto necessitano di questo membro di dati. Se è specifico della classe, dichiara semplicemente una proprietà a cui è possibile accedere pubblicamente o privatamente a seconda delle necessità.


1
Non sono sicuro di essere completamente d'accordo con l'avere HolidayDiscounte l' RentalDiscountimplementazione IDiscountCalculator, perché non sono calcolatori di sconto. Il calcolo viene eseguito in quell'altro metodo getMaxApplicableLengthDiscount. HolidayDiscounte RentalDiscountsono sconti. Uno sconto non viene calcolato, è un tipo di sconto applicabile che viene calcolato o semplicemente selezionato tra una serie di sconti.
user3748908

1
Forse l'interfaccia dovrebbe essere chiamata IDiscountable. Qualsiasi classe debba implementare può. Se le classi di sconto vacanza / affitto sono solo DTO / POCO e memorizzano il risultato anziché calcolarlo, va bene.
Jon Raynor,

3

Per rispondere alla tua domanda: non puoi concludere un odore di codice se un'interfaccia ha solo getter.

Un esempio di metrica fuzzy per gli odori di codice sono interfacce gonfiate con molti metodi (no se getter o no). Tendono a violare il principio di seggregation dell'interfaccia.

A livello semantico, anche il principio della responsabilità unica non dovrebbe essere violato. Tendo ad essere violato se nel contratto di interfaccia vengono trattate diverse problematiche diverse da un argomento. Questo a volte è difficile da identificare e hai bisogno di esperienza per identificarlo.

D'altra parte dovresti essere consapevole se la tua interfaccia definisce setter. Questo perché è probabile che i setter cambino stato, questo è il loro lavoro. La domanda da porsi qui: l'oggetto dietro l'interfaccia effettua una transizione da uno stato valido a un altro stato valido? Ma questo non è principalmente un problema dell'interfaccia ma l'implementazione del contratto di interfaccia dell'oggetto implementante.

L'unica preoccupazione che ho con il tuo approccio è che tu operi su OR-Mapping. In questo piccolo caso questo non è un problema. Tecnicamente va bene. Ma non vorrei operare su oggetti OR-Mappati. Possono avere troppi stati diversi che sicuramente non consideri nelle operazioni fatte su di essi ...

Gli oggetti OR mappati possono essere (presuppongono JPA) transitori, persistenti distaccati invariati, persistenti allegati invariati, persistenti staccati modificati, persistenti allegati modificati ... se si utilizza la convalida bean su quegli oggetti, non è possibile vedere se l'oggetto è stato controllato o meno. Dopotutto puoi identificare più di 10 stati diversi che l'oggetto OR-Mapped può avere. Tutte le tue operazioni gestiscono correttamente questi stati?

Questo non è certamente un problema se la tua interfaccia definisce il contratto e l'implementazione lo segue. Ma per gli oggetti mappati su OR ho ragionevoli dubbi sul fatto che possa adempiere a tale contratto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.