È un'interfaccia con solo i getter un odore di codice?

9

(Ho visto questa domanda , ma la prima risposta riguarda le proprietà automatiche più che sul design, e la seconda dice nasconde il codice di archiviazione dei dati dal consumatore , che io" Non sono sicuro di quello che voglio / il mio codice fa, quindi mi piacerebbe sentire qualche altra opinione)

Ho due entità molto simili, HolidayDiscount e RentalDiscount , che rappresentano sconti di lunghezza come "se dura almeno numberOfDays , uno sconto percent è applicabile". Le tabelle hanno fks 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, un HolidayOffer ha un numero di HolidayDiscounts , e nel calcolare il suo costo, dobbiamo calcolare lo sconto applicabile. Lo stesso per noleggi e RentalDiscounts .

Poiché la logica è la stessa, voglio mantenerla in un unico posto. Questo è ciò che 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 che le due entità implementano, che ovviamente funziona, ma dubito che sia un buon progetto. Non rappresenta alcun comportamento, dato che tenere un valore non è una proprietà. Questo è un caso piuttosto semplice, ho un paio di casi simili e più complessi (con getter di 3-4).

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

    
posta user3748908 01.02.2017 - 16:48
fonte

2 risposte

4

Direi che il tuo design è un po 'errato.

Una possibile soluzione sarebbe creare un'interfaccia chiamata IDiscountCalculator .

decimal CalculateDiscount();

Ora qualsiasi classe che deve fornire uno sconto implementerà questa interfaccia. Se lo sconto utilizza il numero di giorni, così sia, l'interfaccia non si preoccupa molto di quello che è i dettagli di implementazione.

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

    
risposta data 01.02.2017 - 19:55
fonte
2

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 (non se getter o no). Tendono a violare il principio di segregazione dell'interfaccia.

Sul piano semantico non va violato anche il principio di responsabilità individuale. Tendo ad essere violato se vedi diversi problemi gestiti nel contratto di interfaccia che non sono un argomento. Questo a volte è difficile da identificare e hai bisogno di esperienza per identificarlo.

Dall'altro lato dovresti sapere se la tua interfaccia definisce setter. Questo perché i setter sono suscettibili di cambiare stato, questo è il loro lavoro. La domanda da porre qui: l'oggetto dietro l'interfaccia effettua una transizione da uno stato valido in un altro stato valido. Ma questo non è principalmente un problema dell'interfaccia, ma l'implementazione del contratto di interfaccia dell'oggetto di implementazione.

L'unica preoccupazione che ho con il tuo approccio è che tu operi su OR-Mapping. In questo piccolo caso questo non è un problema. Tecnicamente è ok. Ma non opererei su oggetti OR mappati. Possono avere troppi stati diversi che sicuramente non considerano nelle operazioni eseguite su di loro ...

Gli oggetti mappati OR possono essere (presuppongono JPA) transitori, persistenti distaccati invariati, persistenti attaccati invariati, persistenti stacchi modificati, persistenti allegati modificati ... se si utilizza la convalida del bean su quegli oggetti non si può più vedere se l'oggetto è stato controllato o no. Dopo tutto è possibile identificare più di 10 diversi stati che l'oggetto OR mappato 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 in OR ho ragionevoli dubbi sulla possibilità di completare un simile contratto.

    
risposta data 02.02.2017 - 11:54
fonte