แม้ว่านี่อาจเป็นคำถามที่ไม่เชื่อเรื่องภาษา แต่ฉันก็สนใจที่จะตอบคำถามเกี่ยวกับระบบนิเวศ. 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 และป้องกันโดเมนโรคโลหิตจาง (ซึ่งได้รับมอบหมายให้ทำลาย)