Bu bir programlama dili agnostik sorusu olsa da, .NET ekosistemini hedefleyen cevaplarla ilgileniyorum.
Bu senaryo: kamu yönetimi için basit bir konsol uygulaması geliştirmemiz gerektiğini varsayalım. Uygulama araç vergisi ile ilgili. Onlar (sadece) aşağıdaki iş kurallarına sahiptir:
1.a) Eğer araç bir araba ise ve sahibinin son ödeme yaptığı tarihte vergi 30 gün önceydi, bu durumda işletme sahibinin tekrar ödeme yapması gerekiyordu.
1.b) Eğer araç bir motosiklet ise ve sahibi en son vergiyi 60 gün önce ödediyse, sahibi yeniden ödemek zorunda kalır.
Başka bir deyişle, bir arabanız varsa, her 30 günde bir ödemek zorundasınız veya bir motosikletiniz varsa, her 60 günde bir ödemek zorundasınız.
Sistemdeki her araç için, uygulama bu kuralları test etmeli ve bunları karşılamayan araçları (plaka numarası ve sahip bilgileri) yazdırmalıdır.
İstediğim şey:
2.a) KATI ilkelerine uygun (özellikle Açık / kapalı prensibi).
İstemediğim şey (sanırım):
2.b) Anemik bir etki alanı, bu nedenle iş mantığı iş varlıklarının içine girmelidir.
Bununla başladım:
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: Açık / kapalı prensibi garanti edilir; Bisiklet vergisini istiyorlarsa, henüz Araçtan devralırsanız, HasToPay yöntemini geçersiz kılarsınız ve bitirdiniz. Açık / kapalı prensip basit kalıtım yoluyla sağlanmıştır.
"Sorunlar":
3.a) Bir araç ödemek zorunda olup olmadığını neden bilmek zorunda? Kamu Yönetimi endişesi değil mi? Kamu idaresi araç vergisini değiştirirse, neden Araba değişmeli? Bence şunu sormalısınız: "öyleyse neden Araç içine bir HasToPay yöntemi koydunuz?" Ve cevabı şudur: Çünkü PublicAdministration içindeki araç tipini test etmek istemiyorum. Daha iyi bir alternatif görüyor musun?
3.b) Belki vergi ödemesi her şeyden önce araçla ilgili bir meseledir, veya belki de ilk Şahıs-Araç-Otomobil-Motosiklet-Kamu Yönetimi tasarımım tamamen yanlış ya da bir filozofa ve daha iyi bir iş analistine ihtiyacım var. Bir çözüm olabilir: HasToPay yöntemini başka bir sınıfa taşıyın, buna TaxPayment diyebiliriz. Sonra iki TaxPayment türevi sınıf oluşturduk; CarTaxPayment ve MotosikletTaxPayment. Ardından, Araç sınıfına bir soyut Ödeme özelliği (TaxPayment türünden) ekliyoruz ve doğru TaxPayment örneğini Araba ve Motosiklet sınıflarından alıyoruz:
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; }
}
}
Ve biz eski süreci şu şekilde çağırırız:
public IEnumerable<Vehicle> GetVehiclesThatHaveToPay()
{
return this.GetAllVehicles().Where(vehicle => vehicle.Payment.HasToPay());
}
Ama şimdi bu kod derlenmeyecek çünkü CarTaxPayment / MotorbikeTaxPayment / TaxPayment içinde bir LastPaidTime üyesi yok. CarTaxPayment ve MotorbikeTaxPayment'i iş varlıklarından ziyade "algoritma uygulamaları" gibi görmeye başlıyorum. Her nasılsa şimdi bu "algoritmalar" LastPaidTime değerine ihtiyaç duyuyor. Elbette, değeri TaxPayment'in kurucusuna aktarabiliriz, ancak bu araç kapsüllemesini veya sorumluluklarını ihlal eder ya da OOP misyonerleri ne diyorsa söylesin değil mi?
3.c) Varsayalım ki zaten bir Varlık Çerçevesi ObjectContext (etki alanı nesnelerimizi kullanır; Kişi, Araç, Araba, Motosiklet, Kamu İdaresi). PublicAdministration.GetAllVehicles yönteminden bir ObjectContext başvurusu almak ve bu nedenle işlevselliği uygulamak için ne yapardınız?
3.d) Ayrıca CarTaxPayment.HasToPay için aynı ObjectContext referansına, ancak "enjekte" işleminden sorumlu olan MotorbikeTaxPayment.HasToPay için farklı bir referansa ihtiyacımız olursa, bu başvuruları ödeme sınıflarına nasıl iletirsiniz? Bu durumda, anemik bir alandan (ve dolayısıyla hiçbir hizmet nesnesinden yoksun) kaçınmak bizi felakete sürüklemiyor mu?
Bu basit senaryo için tasarım tercihleriniz nelerdir? Açıkçası gösterdiğim örnekler kolay bir görev için "çok karmaşık", ancak aynı zamanda fikir SOLID ilkelerine bağlı kalmak ve anemik etki alanlarını (yıkılmak üzere görevlendirilmiş) önlemek.