Apakah antarmuka dengan pengambil hanya bau kode?

10

(Saya sudah melihat pertanyaan ini , tetapi jawaban pertama tentang properti otomatis lebih dari tentang desain, dan jawaban kedua mengatakan menyembunyikan kode penyimpanan data dari konsumen , yang saya tidak yakin apa yang saya inginkan / kode saya tidak, jadi saya ingin mendengar pendapat lain)

Saya memiliki dua entitas yang sangat mirip, HolidayDiscountdan RentalDiscount, yang mewakili diskon panjang sebagai 'jika bertahan setidaknya numberOfDays, percentdiskon berlaku'. Tabel memiliki fks ke entitas induk yang berbeda, dan digunakan di tempat yang berbeda, tetapi di mana mereka digunakan, ada logika umum untuk mendapatkan diskon maksimum yang berlaku. Misalnya, a HolidayOffermemiliki sejumlah HolidayDiscounts, dan ketika menghitung biayanya, kita perlu mencari tahu diskon yang berlaku. Sama untuk rental dan RentalDiscounts.

Karena logikanya sama, saya ingin menyimpannya di satu tempat. Itulah yang dilakukan oleh metode, predikat, dan pembanding berikut:

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

Karena satu-satunya informasi yang diperlukan adalah jumlah hari, saya berakhir dengan antarmuka sebagai

public interface LengthDiscount {

    Integer getNumberOfDays();
}

Dan dua entitas

@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;
    }
}

Antarmuka memiliki metode pengambil tunggal yang diterapkan dua entitas, yang tentu saja berfungsi, tapi saya ragu itu desain yang bagus. Itu tidak mewakili perilaku apa pun, mengingat memegang nilai bukanlah properti. Ini adalah kasus yang agak sederhana, saya punya beberapa kasus yang serupa, lebih kompleks (dengan 3-4 getter).

Pertanyaan saya adalah, apakah ini desain yang buruk? Apa pendekatan yang lebih baik?

pengguna3748908
sumber
4
Tujuan dari antarmuka adalah untuk membangun pola koneksi, bukan untuk merepresentasikan perilaku. Tugas itu jatuh ke metode dalam implementasi.
Robert Harvey
Mengenai implementasi Anda, ada banyak sekali boilerplate (kode yang tidak benar-benar melakukan apa pun, selain menyediakan struktur). Apakah Anda yakin perlu semua itu?
Robert Harvey
Bahkan metode antarmuka hanyalah 'getter' itu tidak berarti Anda tidak dapat mengimplementasikan 'setter' pada implementasi (jika semua memiliki setter, s Anda masih dapat menggunakan abstrak)
рüффп

Jawaban:

5

Saya akan mengatakan desain Anda agak salah arah.

Salah satu solusi potensial adalah membuat antarmuka yang disebut IDiscountCalculator.

decimal CalculateDiscount();

Sekarang setiap kelas yang perlu memberikan diskon akan mengimplementasikan antarmuka ini. Jika diskon menggunakan jumlah hari, ya, antarmuka tidak terlalu peduli karena itu adalah detail implementasi.

Jumlah hari mungkin termasuk dalam semacam kelas dasar abstrak jika semua kelas diskon membutuhkan anggota data ini. Jika itu adalah kelas khusus, maka cukup deklarasikan properti yang dapat diakses secara publik atau pribadi tergantung pada kebutuhan.

Jon Raynor
sumber
1
Saya tidak yakin saya sepenuhnya setuju dengan memiliki HolidayDiscountdan RentalDiscountmenerapkan IDiscountCalculator, karena mereka bukan kalkulator diskon. Perhitungan dilakukan dengan metode lain getMaxApplicableLengthDiscount. HolidayDiscountdan RentalDiscountdiskon. Diskon tidak dihitung, adalah jenis diskon yang berlaku apa yang dihitung, atau hanya dipilih di antara sejumlah diskon.
user3748908
1
Mungkin Antarmuka harus disebut IDiscountable. Kelas apa pun yang perlu diimplementasikan bisa. Jika kelas diskon liburan / sewa hanya DTO / POCO dan simpan hasilnya daripada menghitung itu tidak masalah.
Jon Raynor
3

Untuk menjawab pertanyaan Anda: Anda tidak dapat menyimpulkan bau kode jika antarmuka hanya memiliki getter.

Contoh untuk metrik fuzzy untuk bau kode adalah antarmuka yang membengkak dengan banyak metode (tidak jika getter atau tidak). Mereka cenderung melanggar prinsip pemisahan antarmuka.

Pada tingkat semantik, prinsip tanggung jawab tunggal tidak boleh dilanggar juga. Saya cenderung dilanggar jika Anda melihat beberapa masalah berbeda ditangani dalam kontrak antarmuka yang bukan merupakan satu subjek. Ini terkadang sulit untuk diidentifikasi dan Anda perlu beberapa pengalaman untuk mengidentifikasinya.

Di sisi lain Anda harus waspada jika antarmuka Anda mendefinisikan setter. Itu karena pemukim cenderung mengubah keadaan, itulah pekerjaan mereka. Pertanyaan untuk diajukan di sini: Apakah objek di belakang antarmuka membuat transisi dari status yang valid ke status valid lainnya. Tapi itu bukan terutama masalah antarmuka tetapi implementasi kontrak antarmuka objek pelaksana.

Satu-satunya masalah yang saya miliki dengan pendekatan Anda adalah bahwa Anda beroperasi pada OR-Mappings. Dalam kasus kecil ini ini bukan masalah. Secara teknis tidak masalah. Tapi saya tidak akan beroperasi pada objek yang dipetakan OR. Mereka dapat memiliki terlalu banyak negara berbeda yang pasti tidak Anda pertimbangkan dalam operasi yang dilakukan pada mereka ...

Objek yang dipetakan OR mungkin (mengandaikan JPA) transient, persistent detached, tidak berubah, persistent attached tidak berubah, persistent detached berubah, persistent dilampirkan berubah ... jika Anda menggunakan validasi kacang pada objek-objek yang selanjutnya Anda tidak dapat melihat apakah objek diperiksa atau tidak. Setelah semua Anda dapat mengidentifikasi lebih dari 10 negara yang berbeda objek OR-Mapped dapat memiliki. Apakah semua operasi Anda menangani keadaan ini dengan benar?

Ini tentu bukan masalah jika antarmuka Anda mendefinisikan kontrak dan implementasinya mengikutinya. Tetapi untuk objek yang dipetakan OR saya ragu-ragu yang wajar dapat memenuhi kontrak seperti itu.

oopexpert
sumber