Meskipun ini bisa menjadi pertanyaan agnostik bahasa pemrograman, saya tertarik pada jawaban yang menargetkan ekosistem .NET.
Ini adalah skenario: misalkan kita perlu mengembangkan aplikasi konsol sederhana untuk administrasi publik. Aplikasi ini tentang pajak kendaraan. Mereka (hanya) memiliki aturan bisnis berikut:
1.a) Jika kendaraan adalah mobil dan terakhir kali pemiliknya membayar pajak adalah 30 hari yang lalu maka pemilik harus membayar lagi.
1.b) Jika kendaraan itu adalah sepeda motor dan terakhir kali pemiliknya membayar pajak adalah 60 hari yang lalu maka pemilik harus membayar lagi.
Dengan kata lain, jika Anda memiliki mobil, Anda harus membayar setiap 30 hari atau jika Anda memiliki sepeda motor, Anda harus membayar setiap 60 hari.
Untuk setiap kendaraan dalam sistem, aplikasi harus menguji aturan tersebut dan mencetak kendaraan tersebut (nomor plat dan informasi pemilik) yang tidak memuaskan mereka.
Yang saya inginkan adalah:
2.a) Sesuai dengan prinsip SOLID (terutama prinsip Terbuka / tertutup).
Yang tidak saya inginkan adalah (saya pikir):
2.b) Domain anemia, oleh karena itu logika bisnis harus masuk ke dalam entitas bisnis.
Saya mulai dengan ini:
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: Prinsip Terbuka / tertutup dijamin; jika mereka menginginkan pajak sepeda yang baru saja Anda warisi dari Kendaraan, maka Anda mengganti metode HasToPay dan Anda selesai. Prinsip terbuka / tertutup dipenuhi melalui warisan sederhana.
"Masalahnya" adalah:
3.a) Mengapa kendaraan harus tahu jika harus membayar? Bukan masalah Administrasi Publik? Jika administrasi publik mengatur perubahan pajak mobil, mengapa Mobil harus berubah? Saya pikir Anda harus bertanya: "lalu mengapa Anda memasukkan metode HasToPay di dalam Vehicle?", Dan jawabannya adalah: karena saya tidak ingin menguji untuk jenis kendaraan (typeof) di dalam Administrasi Publik. Apakah Anda melihat alternatif yang lebih baik?
3.b) Mungkin pembayaran pajak adalah masalah kendaraan, atau mungkin desain awal Person-Vehicle-Car-Motorbike-Publicadalah salah, atau saya butuh filsuf dan analis bisnis yang lebih baik. Solusi dapat berupa: pindahkan metode HasToPay ke kelas lain, kita dapat menyebutnya Pembayaran Pajak. Lalu, kami membuat dua kelas turunan Pembayaran Pajak; Pembayaran CarTax dan Pembayaran Motor. Kemudian kami menambahkan properti Pembayaran abstrak (dari jenis Pembayaran Pajak) ke kelas Kendaraan dan kami mengembalikan contoh Pembayaran Pajak yang benar dari kelas Mobil dan Sepeda Motor:
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; }
}
}
Dan kami menerapkan proses lama dengan cara ini:
public IEnumerable<Vehicle> GetVehiclesThatHaveToPay()
{
return this.GetAllVehicles().Where(vehicle => vehicle.Payment.HasToPay());
}
Tetapi sekarang kode ini tidak dapat dikompilasi karena tidak ada anggota LastPaidTime di dalam CarTaxPayment / MotorbikeTaxPayment / TaxPayment. Saya mulai melihat CarTaxPayment dan MotorbikeTaxPayment lebih seperti "implementasi algoritma" daripada entitas bisnis. Entah bagaimana sekarang "algoritma" tersebut membutuhkan nilai LastPaidTime. Tentu, kita dapat memberikan nilainya kepada konstruktor Pembayaran Pajak, tetapi itu akan melanggar enkapsulasi kendaraan, atau tanggung jawab, atau apa pun yang disebut oleh para penginjil OOP itu, bukan?
3.c) Misalkan kita sudah memiliki Entitas Kerangka ObjectContext (yang menggunakan objek domain kita; Orang, Kendaraan, Mobil, Sepeda Motor, Administrasi Publik). Bagaimana yang Anda lakukan untuk mendapatkan referensi ObjectContext dari metode PublicAdministration.GetAllVehicles dan karenanya mengimplementasikan funcionality?
3.d) Jika kita juga memerlukan referensi ObjectContext yang sama untuk CarTaxPayment.HasToPay tetapi berbeda untuk MotorbikeTaxPayment.HasToPay, siapa yang bertanggung jawab atas "menyuntikkan" atau bagaimana Anda meneruskan referensi tersebut ke kelas pembayaran? Dalam hal ini, menghindari domain yang anemia (dan karenanya tidak ada objek layanan), tidak membawa kita ke bencana?
Apa pilihan desain Anda untuk skenario sederhana ini? Jelas contoh yang saya tunjukkan "terlalu rumit" untuk tugas yang mudah, tetapi pada saat yang sama idenya adalah untuk mematuhi prinsip-prinsip SOLID dan mencegah domain anemia (yang telah ditugaskan untuk menghancurkan).