อินเทอร์เฟซที่มีเพียง getters เป็นกลิ่นรหัส


10

(ฉันเคยเห็นคำถามนี้แต่คำตอบแรกเป็นเรื่องเกี่ยวกับคุณสมบัติอัตโนมัติมากกว่าเกี่ยวกับการออกแบบและคำตอบที่สองบอกว่าซ่อนรหัสการจัดเก็บข้อมูลจากผู้บริโภคซึ่งฉันไม่แน่ใจว่าเป็นสิ่งที่ฉันต้องการ / รหัสของฉันทำ ดังนั้นฉันต้องการฟังความเห็นอื่น ๆ )

ฉันมีเอนทิตีที่คล้ายกันมากสองรายการHolidayDiscountและRentalDiscountนั่นคือส่วนลดที่มีความยาวเนื่องจาก 'ถ้าอย่างน้อยที่สุดจะnumberOfDaysใช้percentส่วนลดได้' ตารางมี fks ไปยังเอนทิตีพาเรนต์ที่แตกต่างกันและใช้ในสถานที่ต่าง ๆ แต่ที่ที่ใช้จะมีตรรกะทั่วไปเพื่อรับส่วนลดสูงสุดที่เกี่ยวข้อง ตัวอย่างเช่น a HolidayOfferมีจำนวนHolidayDiscountsและเมื่อคำนวณต้นทุนเราต้องหาส่วนลดที่เกี่ยวข้อง RentalDiscountsเหมือนกันสำหรับเช่าและ

เนื่องจากตรรกะเหมือนกันฉันต้องการเก็บไว้ในที่เดียว นั่นคือสิ่งที่วิธีต่อไปนี้เพรดิเคตและตัวเปรียบเทียบทำ:

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

เนื่องจากข้อมูลที่จำเป็นเท่านั้นคือจำนวนวันฉันจึงสิ้นสุดด้วยส่วนต่อประสานเป็น

public interface LengthDiscount {

    Integer getNumberOfDays();
}

และหน่วยงานทั้งสอง

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

อินเทอร์เฟซมีวิธีทะเยอทะยานเดียวที่ทั้งสองเอนทิตีใช้งานซึ่งแน่นอนว่าใช้งานได้ แต่ฉันสงสัยว่ามันเป็นการออกแบบที่ดี มันไม่ได้เป็นตัวแทนพฤติกรรมใด ๆ ที่ได้รับถือค่าไม่ใช่ทรัพย์สิน นี่เป็นกรณีที่ค่อนข้างง่ายฉันมีสองสามกรณีที่คล้ายกันและซับซ้อนมากขึ้น (ด้วย 3-4 getters)

คำถามของฉันคือนี่เป็นการออกแบบที่ไม่ดีหรือไม่? แนวทางที่ดีกว่าคืออะไร?


4
วัตถุประสงค์ของอินเทอร์เฟซคือการสร้างรูปแบบการเชื่อมต่อไม่ใช่เพื่อแสดงพฤติกรรม หน้าที่นั้นตกอยู่กับวิธีการในการปฏิบัติ
Robert Harvey

สำหรับการนำไปใช้ของคุณนั้นมีจำนวนมากสำเร็จรูป (รหัสที่ไม่ได้ทำอะไรเลยนอกจากโครงสร้างให้) คุณแน่ใจหรือว่าต้องการทั้งหมด
Robert Harvey

แม้เมธอดของอินเทอร์เฟซเป็นเพียงแค่ 'getters' แต่ก็ไม่ได้หมายความว่าคุณไม่สามารถใช้ 'setters' ในการติดตั้งได้ (ถ้าทุกคนมี setter คุณยังสามารถใช้บทคัดย่อได้)
рüффп

คำตอบ:


5

ฉันจะบอกว่าการออกแบบของคุณผิดไปเล็กน้อย

IDiscountCalculatorหนึ่งในวิธีการแก้ปัญหาที่อาจเกิดขึ้นจะสร้างอินเตอร์เฟซที่เรียกว่า

decimal CalculateDiscount();

ตอนนี้คลาสที่ต้องการให้ส่วนลดจะใช้อินเทอร์เฟซนี้ หากส่วนลดใช้จำนวนวันดังนั้นไม่ว่าอินเทอร์เฟซจะไม่สนใจจริงๆนั่นคือรายละเอียดการใช้งาน

จำนวนวันอาจเป็นของคลาสพื้นฐานบางอย่างถ้าคลาสส่วนลดทั้งหมดต้องการสมาชิกข้อมูลนี้ ถ้าเป็นคลาสเฉพาะให้ประกาศคุณสมบัติที่สามารถเข้าถึงได้แบบสาธารณะหรือแบบส่วนตัวโดยขึ้นอยู่กับความต้องการ


1
ฉันไม่แน่ใจว่าฉันเห็นด้วยอย่างสมบูรณ์กับการมีHolidayDiscountและRentalDiscountการใช้งานIDiscountCalculatorเพราะพวกเขาไม่ใช่เครื่องคิดเลขลดราคา getMaxApplicableLengthDiscountการคำนวณจะทำในวิธีการอื่นว่า HolidayDiscountและRentalDiscountเป็นส่วนลด ไม่มีการคำนวณส่วนลดเป็นประเภทของส่วนลดที่ใช้บังคับสิ่งที่คำนวณหรือเลือกเพียงจำนวนส่วนลด
3748908

1
บางทีอินเตอร์เฟสควรจะเรียกว่า IDiscountable สิ่งที่ชั้นเรียนจำเป็นต้องใช้สามารถ หากคลาสส่วนลดวันหยุด / เช่าเป็นเพียง DTO / POCO และเก็บผลลัพธ์แทนการคำนวณที่ใช้ได้
Jon Raynor

3

ในการตอบคำถามของคุณ: คุณไม่สามารถสรุปได้ว่ามีกลิ่นของรหัสหรือไม่หากอินเทอร์เฟซมีเพียง getters

ตัวอย่างสำหรับตัวชี้วัดเลือนสำหรับกลิ่นรหัสคือส่วนต่อประสานที่ป่องด้วยวิธีการมากมาย (ไม่ได้ถ้า getters หรือไม่) พวกเขามีแนวโน้มที่จะละเมิดหลักการการแบ่งส่วนต่อประสาน

ในระดับความหมายหลักการความรับผิดชอบเดียวไม่ควรละเมิดเช่นกัน ฉันมีแนวโน้มที่จะถูกละเมิดหากคุณเห็นปัญหาต่าง ๆ ที่จัดการในสัญญาส่วนต่อประสานที่ไม่ใช่หัวข้อเดียว นี่เป็นบางครั้งยากที่จะระบุและคุณต้องการประสบการณ์บางอย่างในการระบุ

ในอีกด้านหนึ่งคุณควรทราบว่าอินเทอร์เฟซของคุณกำหนด setters นั่นเป็นเพราะ setters มีแนวโน้มที่จะเปลี่ยนสถานะนั่นคืองานของพวกเขา คำถามที่ถามที่นี่: วัตถุที่อยู่เบื้องหลังส่วนต่อประสานทำการเปลี่ยนแปลงจากสถานะที่ถูกต้องไปเป็นสถานะที่ถูกต้องอื่นหรือไม่ แต่นั่นไม่ใช่ปัญหาของอินเทอร์เฟซ แต่เป็นการดำเนินการตามสัญญาอินเทอร์เฟซของวัตถุที่ใช้งาน

ข้อกังวลเดียวที่ฉันมีกับแนวทางของคุณคือคุณทำงานในการแมปหรือ ในกรณีนี้นี่ไม่ใช่ปัญหา ในทางเทคนิคมันก็โอเค แต่ฉันจะไม่ทำงานกับวัตถุที่แมปหรือ พวกเขาสามารถมีสถานะที่แตกต่างกันมากเกินไปที่คุณไม่ได้พิจารณาในการดำเนินการกับพวกเขา ...

หรือวัตถุที่แมปอาจเป็น (สันนิษฐานว่า JPA) ชั่วคราวไม่เปลี่ยนแปลงไม่เปลี่ยนแปลงถาวรที่แนบมาไม่เปลี่ยนแปลงถาวรแยกเปลี่ยนแปลงถาวรที่แนบมาเปลี่ยนแปลง ... ถ้าคุณใช้การตรวจสอบถั่วในวัตถุเหล่านั้นคุณยังไม่สามารถดูว่าวัตถุถูกตรวจสอบหรือไม่ หลังจากทั้งหมดคุณสามารถระบุมากกว่า 10 รัฐที่แตกต่างกันวัตถุที่แมปสามารถมี การดำเนินการทั้งหมดของคุณจัดการกับสถานะเหล่านี้ถูกต้องหรือไม่?

แน่นอนว่าไม่มีปัญหาหากอินเทอร์เฟซของคุณกำหนดสัญญาและการปฏิบัติตามนั้น แต่สำหรับวัตถุที่แมปหรือฉันมีข้อสงสัยที่สมเหตุสมผลสามารถเติมเต็มสัญญาดังกล่าว

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.