SOLID, หลีกเลี่ยงโดเมนโลหิตจาง, การฉีดพึ่งพา?


33

แม้ว่านี่อาจเป็นคำถามที่ไม่เชื่อเรื่องภาษา แต่ฉันก็สนใจที่จะตอบคำถามเกี่ยวกับระบบนิเวศ. NET

นี่คือสถานการณ์สมมติว่าเราจำเป็นต้องพัฒนาแอปพลิเคชั่นคอนโซลอย่างง่ายสำหรับการบริหารสาธารณะ แอปพลิเคชันเกี่ยวกับภาษีรถยนต์ พวกเขา (เท่านั้น) มีกฎเกณฑ์ทางธุรกิจดังต่อไปนี้:

1.a) หากยานพาหนะเป็นรถยนต์และครั้งสุดท้ายที่เจ้าของชำระภาษีคือ 30 วันที่ผ่านมาเจ้าของจะต้องจ่ายอีกครั้ง

1.b) หากยานพาหนะเป็นมอเตอร์ไซค์และครั้งสุดท้ายที่เจ้าของชำระภาษีคือ 60 วันที่ผ่านมาเจ้าของจะต้องจ่ายอีกครั้ง

กล่าวอีกนัยหนึ่งถ้าคุณมีรถคุณต้องจ่ายทุก ๆ 30 วันหรือถ้าคุณมีมอเตอร์ไซค์คุณต้องจ่ายทุก ๆ 60 วัน

สำหรับยานพาหนะแต่ละคันในระบบแอปพลิเคชันควรทดสอบกฎเหล่านั้นและพิมพ์ยานพาหนะเหล่านั้น (หมายเลขทะเบียนรถและข้อมูลเจ้าของรถ) ที่ไม่เป็นที่พอใจ

สิ่งที่ฉันต้องการคือ:

2.a) สอดคล้องกับหลักการ SOLID (โดยเฉพาะอย่างยิ่งหลักการ Open / Closed)

สิ่งที่ฉันไม่ต้องการคือ (ฉันคิดว่า):

2.b) โดเมนโลหิตจางดังนั้นตรรกะทางธุรกิจจึงควรเข้าสู่องค์กรธุรกิจ

ฉันเริ่มต้นด้วยสิ่งนี้:

public class Person
// You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.
{
    public string Name { get; set; }

    public string Surname { get; set; }
}

public abstract class Vehicle
{
    public string PlateNumber { get; set; }

    public Person Owner { get; set; }

    public DateTime LastPaidTime { get; set; }

    public abstract bool HasToPay();
}

public class Car : Vehicle
{
    public override bool HasToPay()
    {
        return (DateTime.Today - this.LastPaidTime).TotalDays >= 30;
    }
}

public class Motorbike : Vehicle
{
    public override bool HasToPay()
    {
        return (DateTime.Today - this.LastPaidTime).TotalDays >= 60;
    }
}

public class PublicAdministration
{
    public IEnumerable<Vehicle> GetVehiclesThatHaveToPay()
    {
        return this.GetAllVehicles().Where(vehicle => vehicle.HasToPay());
    }

    private IEnumerable<Vehicle> GetAllVehicles()
    {
        throw new NotImplementedException();
    }
}

class Program
{
    static void Main(string[] args)
    {
        PublicAdministration administration = new PublicAdministration();
        foreach (var vehicle in administration.GetVehiclesThatHaveToPay())
        {
            Console.WriteLine("Plate number: {0}\tOwner: {1}, {2}", vehicle.PlateNumber, vehicle.Owner.Surname, vehicle.Owner.Name);
        }
    }
}

2.a: รับประกันหลักการเปิด / ปิด หากพวกเขาต้องการภาษีจักรยานที่คุณเพิ่งได้รับจากยานพาหนะคุณก็จะแทนที่วิธี HasToPay และคุณก็ทำเสร็จแล้ว หลักการเปิด / ปิดเป็นที่พึงพอใจผ่านการสืบทอดง่ายๆ

"ปัญหา" คือ:

3.a) ทำไมยานพาหนะถึงต้องรู้ว่าจะต้องจ่ายหรือไม่? ไม่ได้เป็นความกังวลของการบริหารสาธารณะ? หากกฎการบริหารสาธารณะสำหรับการเปลี่ยนภาษีรถยนต์ทำไมรถยนต์ต้องเปลี่ยน? ฉันคิดว่าคุณควรถามว่า: "ทำไมคุณใส่ HasToPay เข้าไปในยานพาหนะ?" และคำตอบก็คือ: เพราะฉันไม่ต้องการทดสอบประเภทยานพาหนะ (typeof) ใน PublicAd Administration คุณเห็นทางเลือกที่ดีกว่าไหม?

3.b) การชำระภาษีอาจเป็นเรื่องที่เกี่ยวข้องกับยานพาหนะหรือบางทีการออกแบบบุคคล - รถยนต์ - รถมอเตอร์ไซด์ - สาธารณะ - การบริหารจัดการเบื้องต้นของฉันนั้นผิดธรรมดาหรือฉันต้องการนักปรัชญาและนักวิเคราะห์ธุรกิจที่ดีกว่า วิธีแก้ไขอาจเป็น: ย้ายวิธี HasToPay ไปยังคลาสอื่นเราสามารถเรียกมันว่า TaxPayment จากนั้นเราสร้างคลาสที่ได้รับ TaxPayment สองคลาส; CarTaxPayment และ MotorbikeTaxPayment จากนั้นเราเพิ่มคุณสมบัติการชำระนามธรรม (ประเภท TaxPayment) ในคลาสยานพาหนะและเราคืนอินสแตนซ์ TaxPayment ที่ถูกต้องจากคลาสรถยนต์และมอเตอร์ไซค์:

public abstract class TaxPayment
{
    public abstract bool HasToPay();
}

public class CarTaxPayment : TaxPayment
{
    public override bool HasToPay()
    {
        return (DateTime.Today - this.LastPaidTime).TotalDays >= 30;
    }
}

public class MotorbikeTaxPayment : TaxPayment
{
    public override bool HasToPay()
    {
        return (DateTime.Today - this.LastPaidTime).TotalDays >= 60;
    }
}

public abstract class Vehicle
{
    public string PlateNumber { get; set; }

    public Person Owner { get; set; }

    public DateTime LastPaidTime { get; set; }

    public abstract TaxPayment Payment { get; }
}

public class Car : Vehicle
{
    private CarTaxPayment payment = new CarTaxPayment();
    public override TaxPayment Payment
    {
        get { return this.payment; }
    }
}

public class Motorbike : Vehicle
{
    private MotorbikeTaxPayment payment = new MotorbikeTaxPayment();
    public override TaxPayment Payment
    {
        get { return this.payment; }
    }
}

และเราเรียกกระบวนการเก่าด้วยวิธีนี้:

    public IEnumerable<Vehicle> GetVehiclesThatHaveToPay()
    {
        return this.GetAllVehicles().Where(vehicle => vehicle.Payment.HasToPay());
    }

แต่ตอนนี้รหัสนี้จะไม่รวบรวมเพราะไม่มีสมาชิก LastPaidTime ใน CarTaxPayment / MotorbikeTaxPayment / TaxPayment ฉันเริ่มเห็น CarTaxPayment และ MotorbikeTaxPayment เพิ่มเติมเช่น "การติดตั้งอัลกอริทึม" แทนที่จะเป็นเอนทิตี้ของธุรกิจ อย่างใดตอนนี้ "อัลกอริทึม" เหล่านั้นต้องการค่า LastPaidTime แน่นอนว่าเราสามารถส่งผ่านคุณค่าให้กับผู้สร้าง TaxPayment ได้ แต่นั่นจะเป็นการละเมิดการห่อหุ้มยานพาหนะหรือความรับผิดชอบ

3.c) สมมติว่าเรามี Entity Framework ObjectContext (ซึ่งใช้วัตถุโดเมนของเรา ได้แก่ บุคคลยานพาหนะรถยนต์รถจักรยานยนต์การบริหารสาธารณะ) คุณจะทำอย่างไรเพื่อให้ได้รับการอ้างอิง ObjectContext จาก PublicAdministrator.GetAllVehicles วิธีการและดังนั้นจึงใช้ funcionality?

3.d) ถ้าเราต้องการอ้างอิง ObjectContext เดียวกันสำหรับ CarTaxPaymentHasToPay แต่ต่างจาก MotorbikeTaxPaymentHasToPay ใครเป็นผู้รับผิดชอบในการ "ฉีด" หรือคุณจะส่งการอ้างอิงเหล่านั้นไปยังคลาสการชำระเงินอย่างไร ในกรณีนี้หลีกเลี่ยงโดเมนโลหิตจาง (และไม่มีวัตถุบริการ) อย่านำเราไปสู่หายนะ?

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

คำตอบ:


15

ฉันจะบอกว่าทั้งบุคคลและยานพาหนะไม่ควรรู้ว่าการชำระเงินถึงกำหนดหรือไม่

ตรวจสอบโดเมนปัญหาอีกครั้ง

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

การทดลองทางความคิด

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

=> ความเป็นเจ้าของ

รถยนต์ที่ไม่ได้เป็นของใครยังคงเป็นรถยนต์ แต่ไม่มีใครจ่ายภาษีให้ ในความเป็นจริงคุณไม่ได้จ่ายภาษีสำหรับรถ แต่สำหรับสิทธิพิเศษในการเป็นเจ้าของรถ ดังนั้นข้อเสนอของฉันจะเป็น:

public class Person
{
    public string Name { get; set; }

    public string Surname { get; set; }

    public List<Ownership> getOwnerships();

    public List<Vehicle> getVehiclesOwned();
}

public abstract class Vehicle
{
    public string PlateNumber { get; set; }

    public Ownership currentOwnership { get; set; }

}

public class Car : Vehicle {}

public class Motorbike : Vehicle {}

public abstract class Ownership
{
    public Ownership(Vehicle vehicle, Owner owner);

    public Vehicle Vehicle { get;}

    public Owner CurrentOwner { get; set; }

    public abstract bool HasToPay();

    public DateTime LastPaidTime { get; set; }

   //Could model things like payment history or owner history here as well
}

public class CarOwnership : Ownership
{
    public override bool HasToPay()
    {
        return (DateTime.Today - this.LastPaidTime).TotalDays >= 30;
    }
}

public class MotorbikeOwnership : Ownership
{
    public override bool HasToPay()
    {
        return (DateTime.Today - this.LastPaidTime).TotalDays >= 60;
    }
}

public class PublicAdministration
{
    public IEnumerable<Ownership> GetVehiclesThatHaveToPay()
    {
        return this.GetAllOwnerships().Where(HasToPay());
    }

}

นี่ยังห่างไกลจากความสมบูรณ์แบบและอาจไม่ใช่รหัส C # ที่ถูกต้องจากระยะไกล แต่ฉันหวังว่าคุณจะได้แนวคิด (และบางคนสามารถทำความสะอาดได้) ข้อเสียเพียงอย่างเดียวคือคุณอาจต้องการโรงงานหรือบางสิ่งบางอย่างเพื่อสร้างความถูกต้องCarOwnershipระหว่าง a Carและ aPerson


ฉันคิดว่ายานพาหนะนั้นควรบันทึกภาษีที่จ่ายไปและบุคคลควรบันทึกภาษีที่จ่ายสำหรับยานพาหนะทั้งหมดของพวกเขา สามารถเขียนรหัสภาษีเพื่อให้ชำระภาษีรถยนต์ 1 มิถุนายนและขายในวันที่ 10 มิถุนายนเจ้าของใหม่อาจต้องรับผิดชอบภาษีในวันที่ 10 มิถุนายน 1 กรกฎาคมหรือ 10 กรกฎาคม (และแม้ว่าจะเป็น ปัจจุบันมีวิธีหนึ่งที่สามารถเปลี่ยนแปลงได้) หากกฎ 30 วันเป็นค่าคงที่สำหรับรถยนต์แม้ผ่านการเปลี่ยนแปลงการเป็นเจ้าของ แต่รถยนต์ที่เป็นหนี้โดยไม่มีใครไม่สามารถชำระภาษีได้ฉันขอแนะนำว่าเมื่อซื้อรถยนต์ที่ไม่ได้เป็นเจ้าของเป็นเวลา 30 วันขึ้นไป การจ่ายภาษี ...
Supercat

... ได้รับการบันทึกจากบุคคล "เครดิตภาษียานพาหนะที่ไม่มีกรรมสิทธิ์" ซึ่งเป็นเวลาที่ขายความรับผิดทางภาษีจะเป็นศูนย์หรือสามสิบวันโดยไม่คำนึงว่ายานพาหนะนั้นไม่ได้เป็นเจ้าของนานเท่าใด
supercat

4

ฉันคิดว่าในสถานการณ์เช่นนี้ที่คุณต้องการให้กฎธุรกิจอยู่นอกวัตถุเป้าหมายการทดสอบสำหรับประเภทนั้นมากกว่าที่ยอมรับได้

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

ในโลกแห่งความเป็นจริงเสมียนจะดูประเภทของยานพาหนะและค้นหาข้อมูลภาษีจากสิ่งนั้น ทำไมซอฟต์แวร์ของคุณไม่ควรทำตามกฎ busienss เดียวกัน


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

@DavidRobertJones - ตามหลักแล้วฉันจะค้นหาการชำระเงินครั้งล่าสุดในบันทึกประวัติการชำระเงินตามใบขับขี่รถยนต์ การชำระภาษีเป็นเรื่องเกี่ยวกับภาษีไม่ใช่เรื่องเกี่ยวกับยานพาหนะ
Erik Funkenbusch

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

3

สิ่งที่ฉันไม่ต้องการคือ (ฉันคิดว่า):

2.b) โดเมนโลหิตจางซึ่งหมายถึงตรรกะทางธุรกิจภายในองค์กรธุรกิจ

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

ดังนั้นสาเหตุที่เรียกว่าโรคโลหิตจาง: ชั้นเรียนไม่มีอะไรเลย ..

ฉันจะทำอะไร:

  1. เปลี่ยนคลาสยานพาหนะนามธรรมของคุณเป็นอินเทอร์เฟซ
  2. เพิ่มอินเทอร์เฟซ ITaxPayment ที่ยานพาหนะต้องปฏิบัติ ให้ HasToPay ของคุณอยู่ที่นี่
  3. ลบคลาส MotorbikeTaxPayment, CarTaxPayment, TaxPayment พวกเขาเป็นโรคแทรกซ้อนที่ไม่จำเป็น / ไม่เป็นระเบียบ
  4. ยานพาหนะแต่ละประเภท (รถยนต์, จักรยาน, motorhome) ควรดำเนินการที่ยานพาหนะขั้นต่ำและหากมีการเก็บภาษีควรดำเนินการด้วย ITaxPayment เช่นกัน วิธีนี้คุณสามารถแยกแยะระหว่างยานพาหนะที่ไม่ต้องเสียภาษีและยานพาหนะที่เป็น ... สมมติว่าสถานการณ์นี้ยังคงมีอยู่
  5. ยานพาหนะแต่ละประเภทควรใช้วิธีการที่กำหนดไว้ในอินเทอร์เฟซของคุณภายในขอบเขตของคลาสนั้น
  6. นอกจากนี้เพิ่มวันที่ชำระเงินครั้งล่าสุดลงในอินเทอร์เฟซ ITaxPayment ของคุณ

คุณถูก. แต่เดิมคำถามไม่ได้ถูกกำหนดขึ้นมาฉันก็ลบบางส่วนและความหมายก็เปลี่ยนไป ตอนนี้ได้รับการแก้ไขแล้ว ขอบคุณ!

2

ทำไมยานพาหนะถึงต้องรู้ว่าจะต้องจ่ายหรือไม่? ไม่ได้เป็นความกังวลของการบริหารสาธารณะ?

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

public class Car : Vehicle
{
    public override bool HasToPay()
    {
        return (DateTime.Today - this.LastPaidTime).TotalDays >= 30;
    }
}

public class Motorbike : Vehicle
{
    public override bool HasToPay()
    {
        return (DateTime.Today - this.LastPaidTime).TotalDays >= 60;
    }
}

ตัวอย่างโค้ดสองชุดนี้ต่างกันตามค่าคงที่เท่านั้น แทนที่จะแทนที่ฟังก์ชัน HasToPay () ทั้งหมดจะแทนที่int DaysBetweenPayments()ฟังก์ชัน เรียกว่าแทนที่จะใช้ค่าคงที่ ถ้านี่คือสิ่งที่พวกเขาต่างกันจริงฉันก็จะไปเรียน

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

แน่นอนว่าเราสามารถส่งผ่านคุณค่าให้กับผู้สร้าง TaxPayment ได้ แต่นั่นจะเป็นการละเมิดการห่อหุ้มยานพาหนะหรือความรับผิดชอบ

ไม่ได้จริงๆ วัตถุที่จงใจส่งผ่านข้อมูลไปยังวัตถุอื่นในฐานะที่เป็นพารามิเตอร์นั้นไม่ได้เป็นเรื่องของการห่อหุ้มขนาดใหญ่ ความกังวลที่แท้จริงคือวัตถุอื่น ๆ ที่เข้าถึงภายในวัตถุนี้เพื่อดึงข้อมูลออกมาหรือจัดการข้อมูลภายในวัตถุ


1

คุณอาจจะถูกทาง ปัญหาคือเมื่อคุณได้เห็นว่าไม่มีสมาชิก LastPaidTime ภายใน TaxPayment เป็นสิ่งที่เป็นของแม้ว่ามันจะไม่จำเป็นต้องเปิดเผยต่อสาธารณชนเลย:

public interface ITaxPayment
{
    // Your base TaxPayment class will know the 
    // next due date. But you can easily create
    // one which uses (for example) fixed dates
    bool IsPaymentDue();
}

public interface IVehicle
{
    string Plate { get; }
    Person Owner { get; }
    ITaxPayment LastTaxPayment { get; }
} 

ทุกครั้งที่คุณได้รับการชำระเงินคุณจะสร้างอินสแตนซ์ใหม่ของITaxPaymentนโยบายที่เป็นปัจจุบันซึ่งอาจมีกฎที่แตกต่างไปจากเดิมอย่างสิ้นเชิง


สิ่งสำคัญคือการชำระเงินที่ต้องชำระขึ้นอยู่กับครั้งล่าสุดที่เจ้าของชำระเงิน (ยานพาหนะต่าง ๆ วันที่ครบกำหนดที่แตกต่างกัน) ITaxPayment รู้ได้อย่างไรว่าครั้งสุดท้ายที่ยานพาหนะ (หรือเจ้าของ) ชำระเงินเพื่อทำการคำนวณ

1

ฉันเห็นด้วยกับคำตอบของ @sebastiangeigerว่าปัญหานั้นเกี่ยวกับการเป็นเจ้าของยานพาหนะมากกว่ารถยนต์ ฉันจะแนะนำให้ใช้คำตอบของเขา แต่มีการเปลี่ยนแปลงเล็กน้อย ฉันจะทำให้Ownershipคลาสเป็นคลาสทั่วไป:

public class Vehicle {}

public abstract class Ownership<T>
{
    public T Item { get; set; }

    public DateTime LastPaidTime { get; set; }

    public abstract TimeSpan PaymentInterval { get; }

    public virtual bool HasToPay
    {
        get { return (DateTime.Today - this.LastPaidTime) >= this.PaymentInterval; }
    }
}

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

public class CarOwnership : Ownership<Vehicle>
{
    public override TimeSpan PaymentInterval
    {
        get { return new TimeSpan(30, 0, 0, 0); }
    }
}

public class MotorbikeOwnership : Ownership<Vehicle>
{
    public override TimeSpan PaymentInterval
    {
        get { return new TimeSpan(60, 0, 0, 0); }
    }
}

ตอนนี้รหัสที่แท้จริงของคุณเพียงต้องการที่จะจัดการกับระดับหนึ่ง Ownership<Vehicle>-

public class PublicAdministration
{
    List<Ownership<Vehicle>> VehicleOwnerships { get; set; }

    public IEnumerable<Vehicle> GetVehiclesThatHaveToPay()
    {
        return VehicleOwnerships.Where(x => x.HasToPay).Select(x => x.Item);
    }
}

0

ฉันเกลียดที่จะทำลายมัน แต่คุณมีโดเมนโลหิตจางนั่นคือตรรกะทางธุรกิจนอกวัตถุ หากนี่คือสิ่งที่คุณกำลังทำอยู่นั่นก็โอเค แต่คุณควรอ่านเกี่ยวกับเหตุผลที่ควรหลีกเลี่ยง

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

สิ่งที่คุณต้องการสำหรับตัวอย่างนี้คือ:

public class Vehicle
{
    public Boolean isMotorBike()
    public string PlateNumber { get; set; }
    public String Owner { get; set; }
    public DateTime LastPaidTime { get; set; }
}

public class TaxPaymentCalculator
{
    public List<Vehicle> GetVehiclesThatHaveToPay(List<Vehicle> all)
}

หากคุณต้องการที่จะปฏิบัติตามที่เป็นของแข็งที่เป็นสิ่งที่ดี แต่เป็นลุงบ๊อบกล่าวว่าตัวเองพวกเขาเป็นเพียงวิศวกรรมหลักการ พวกเขาไม่ได้ตั้งใจที่จะใช้อย่างเข้มงวด แต่เป็นแนวทางที่ควรพิจารณา


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

@Garrett Hall ฉันชอบคุณลบคลาส Person ออกจาก "domain" ของฉัน ฉันจะไม่มีชั้นเรียนนั้นตั้งแต่แรกเลยขอโทษด้วย แต่ isMotorBike ไม่เข้าท่า สิ่งที่จะนำไปใช้?

1
@DavidRobertJones: ฉันเห็นด้วยกับ Mystere การเพิ่มisMotorBikeวิธีการไม่ใช่ตัวเลือกการออกแบบ OOP ที่ดี มันเหมือนกับการแทนที่ polymorphism ด้วยรหัสประเภท (แม้ว่าจะเป็นบูลีน)
Groo

@MystereMan เราสามารถปล่อย bools และใช้enum Vehicles
Radarbob

+1 พยายามที่จะไม่แบบโดเมนปัญหา แต่รูปแบบการแก้ปัญหา แหลมคม จำได้ว่า และหลักการแสดงความคิดเห็น ผมเห็นความพยายามมากเกินไปที่ตีความตามตัวอักษรอย่างเข้มงวด ; เรื่องไร้สาระ
Radarbob

0

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

ในขณะที่คุณสามารถเดินไปตามถนนของการมี if / select บนประเภทของวัตถุนี้ไม่สามารถปรับขนาดได้มาก หากภายหลังคุณเพิ่มรถบรรทุกและ Segway และผลักดันจักรยานและรถบัสและเครื่องบิน (อาจดูเหมือนไม่ได้ แต่คุณไม่เคยรู้ด้วยบริการสาธารณะ) จากนั้นสภาพจะยิ่งใหญ่ขึ้น และถ้าคุณต้องการเปลี่ยนกฎสำหรับรถบรรทุกคุณต้องค้นหามันในรายการนั้น

เหตุใดจึงไม่สร้างคุณลักษณะและใช้การสะท้อนเพื่อดึงออกมาจริงๆ บางสิ่งเช่นนี้

[DaysBetweenPayments(30)]
public class Car : Vehicle
{
    // actual properties of the physical vehicle
}

[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class DaysBetweenPaymentsAttribute : Attribute, IPaymentRequiredCalculator
{
    private int _days;

    public DaysBetweenPaymentAttribute(int days)
    {
        _days = days;
    }

    public bool HasToPay(Vehicle vehicle)
    {
        return vehicle.LastPaid.AddDays(_days) <= DateTime.Now;
    }
}

คุณสามารถไปกับตัวแปรแบบคงที่ได้ถ้าสะดวกกว่า

ข้อเสียคือ

  • มันจะช้าลงเล็กน้อยและฉันคิดว่าคุณต้องประมวลผลยานพาหนะจำนวนมาก ถ้านั่นเป็นปัญหาฉันอาจจะใช้มุมมองที่เป็นประโยชน์และยึดติดกับคุณสมบัติเชิงนามธรรมหรือวิธีการเชื่อมต่อ (แม้ว่าฉันจะลองแคชตามประเภทด้วย)

  • คุณจะต้องตรวจสอบเมื่อเริ่มต้นว่าแต่ละคลาสย่อยของยานพาหนะมีแอตทริบิวต์ IPaymentRequiredCalculator (หรืออนุญาตให้ใช้ค่าเริ่มต้น) เนื่องจากคุณไม่ต้องการให้ประมวลผล 1,000 คันเท่านั้นเกิดความผิดพลาดเนื่องจาก Motorbike ไม่มี PaymentPeriod

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


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

@EdWoodcock: ฉันคิดว่าปัญหาแรกเป็นจริงของการแก้ปัญหาส่วนใหญ่และมันจะไม่ต้องกังวลฉันจริงๆ หากพวกเขาต้องการความยืดหยุ่นในระดับนั้นฉันจะให้แอพ DB แก่พวกเขา ในจุดที่สองฉันไม่เห็นด้วยอย่างสมบูรณ์ ณ จุดนี้คุณเพียงแค่เพิ่มยานพาหนะเป็นอาร์กิวเมนต์ที่เป็นตัวเลือกสำหรับ HasToPay () และเพิกเฉยต่อสิ่งที่คุณไม่ต้องการ
pdr

ฉันเดาว่ามันขึ้นอยู่กับแผนระยะยาวที่คุณมีสำหรับแอพรวมถึงข้อกำหนดเฉพาะของลูกค้าว่ามันคุ้มค่าที่จะเสียบเข้ากับฐานข้อมูลหรือไม่ถ้าไม่มีอยู่แล้ว (ฉันมักจะคิดว่าซอฟต์แวร์ทั้งหมดเป็น DB - ขับซึ่งฉันรู้ว่ามันไม่ได้เป็นอย่างนั้นเสมอ) อย่างไรก็ตามในประเด็นที่สองรอบคัดเลือกที่สำคัญคือสง่างาม ; ฉันจะไม่พูดว่าการเพิ่มพารามิเตอร์พิเศษให้กับวิธีการในคุณลักษณะที่ใช้อินสแตนซ์ของคลาสที่มีการแนบแอตทริบิวต์นั้นเป็นโซลูชันที่สวยงามเป็นพิเศษ แต่ก็ขึ้นอยู่กับความต้องการของลูกค้าของคุณอีกครั้ง
Ed James

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