SOLID, menghindari domain anemia, injeksi ketergantungan?

33

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).

David Robert Jones
sumber

Jawaban:

15

Saya akan mengatakan bahwa baik orang maupun kendaraan tidak boleh tahu apakah pembayaran sudah jatuh tempo.

memeriksa kembali domain masalah

Jika Anda melihat domain masalah "orang yang memiliki mobil": Untuk membeli mobil, Anda memiliki semacam kontrak yang mengalihkan kepemilikan dari satu orang ke orang lain, baik mobil maupun orang tersebut tidak berubah dalam proses tersebut. Model Anda benar-benar kurang dari itu.

eksperimen pikiran

Dengan asumsi ada pasangan yang sudah menikah, pria itu memiliki mobil dan dia membayar pajak. Sekarang pria itu meninggal, istrinya mewarisi mobil dan akan membayar pajak. Namun, piring Anda akan tetap sama (setidaknya di negara saya mereka akan). Itu masih mobil yang sama! Anda harus dapat memodelkan itu di domain Anda.

=> Kepemilikan

Mobil yang tidak dimiliki oleh siapa pun masih mobil tetapi tidak ada yang akan membayar pajak untuk itu. Bahkan, Anda tidak membayar pajak untuk mobil tetapi untuk hak istimewa memiliki mobil . Jadi proposisi saya adalah:

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

}

Ini jauh dari sempurna dan mungkin bahkan kode C # yang jauh tidak benar tapi saya harap Anda mendapatkan ide (dan seseorang bisa membersihkan ini). Satu-satunya downside adalah bahwa Anda mungkin perlu pabrik atau sesuatu untuk membuat yang benar CarOwnershipantara a Cardan aPerson

sebastiangeiger
sumber
Saya pikir kendaraan itu sendiri harus mencatat pajak yang dibayarkan untuk itu, dan individu harus mencatat pajak yang dibayarkan untuk semua kendaraan mereka. Kode pajak dapat ditulis sehingga jika pajak pada mobil dibayar 1 Juni dan dijual pada 10 Juni, pemilik baru mungkin akan dikenakan pajak pada 10 Juni, 1 Juli, atau 10 Juli (dan bahkan jika itu saat ini salah satu caranya bisa berubah). Jika aturan 30 hari adalah konstan untuk mobil walaupun melalui perubahan kepemilikan, tetapi mobil yang dihutang oleh siapa pun tidak dapat membayar pajak, maka saya akan menyarankan bahwa ketika sebuah mobil yang tidak dimiliki selama 30 hari atau lebih dibeli, sebuah pembayaran pajak ...
supercat
... direkam dari orang "Kredit kendaraan pajak" fiktif, sehingga pada saat penjualan, kewajiban pajak akan menjadi nol atau tiga puluh hari, terlepas dari berapa lama kendaraan tersebut tidak dimiliki.
supercat
4

Saya pikir dalam situasi seperti ini, di mana Anda ingin aturan bisnis ada di luar objek target, pengujian untuk tipe lebih dari dapat diterima.

Tentu saja, dalam banyak situasi Anda harus menggunakan pola Strategi dan membiarkan objek menangani hal-hal itu sendiri, tetapi itu tidak selalu diinginkan.

Di dunia nyata, petugas akan melihat jenis kendaraan dan mencari data pajak berdasarkan itu. Mengapa perangkat lunak Anda tidak harus mengikuti aturan bisnis yang sama?

Erik Funkenbusch
sumber
Ok, katakan Anda meyakinkan saya dan dari metode GetVehiclesThatHaveToPay kami memeriksa jenis kendaraan. Siapa yang harus bertanggung jawab atas LastPaidTime? Apakah LastPaidTime masalah Kendaraan atau Administrasi Publik? Karena jika itu Kendaraan, bukankah logika bisnis itu berada di Kendaraan? Apa yang bisa Anda ceritakan tentang pertanyaan 3.c?
@DavidRobertJones - Idealnya, saya akan mencari pembayaran terakhir di log Riwayat pembayaran, berdasarkan lisensi kendaraan. Pembayaran pajak adalah masalah pajak, bukan masalah kendaraan.
Erik Funkenbusch
5
@ DavidRobertJones Saya pikir Anda membingungkan diri sendiri dengan metafora Anda sendiri. Apa yang Anda miliki bukanlah kendaraan aktual yang perlu melakukan semua hal yang dilakukan kendaraan. Sebaliknya, Anda telah memberi nama, untuk kemudahan Kendaraan Kena Pajak (atau yang serupa) Kendaraan. Seluruh domain Anda adalah pembayaran pajak. Jangan khawatir tentang apa yang dilakukan kendaraan sebenarnya. Dan, jika perlu jatuhkan metafora dan datang dengan yang lebih tepat.
3

Yang tidak saya inginkan adalah (saya pikir):

2.b) Domain anemia, yang berarti logika bisnis di dalam entitas bisnis.

Anda memiliki ini mundur. Domain anemia adalah domain yang logika berada di luar entitas bisnis. Ada banyak alasan mengapa menggunakan kelas tambahan untuk mengimplementasikan logika adalah ide yang buruk; dan hanya pasangan untuk mengapa Anda harus melakukannya.

Karenanya alasan mengapa disebut anemik: kelas tidak memiliki apa pun di dalamnya ..

Apa yang akan saya lakukan:

  1. Ubah kelas Kendaraan abstrak Anda menjadi antarmuka.
  2. Tambahkan antarmuka ITaxPayment yang harus diterapkan oleh kendaraan. Biarkan HasToPay Anda pergi dari sini.
  3. Hapus kelas MotorbikeTaxPayment, CarTaxPayment, TaxPayment. Mereka adalah komplikasi / kekacauan yang tidak perlu.
  4. Setiap jenis kendaraan (mobil, sepeda, motor) harus menerapkan pada Kendaraan minimum dan, jika dikenakan pajak, harus menerapkan Pembayaran ITax juga. Dengan cara ini Anda dapat melukiskan antara kendaraan yang tidak dikenakan pajak dan kendaraan yang ... Mengasumsikan situasi ini ada.
  5. Setiap jenis kendaraan harus menerapkan, dalam batas-batas kelas itu sendiri, metode yang didefinisikan dalam antarmuka Anda.
  6. Juga, tambahkan tanggal pembayaran terakhir ke antarmuka ITaxPayment Anda.
Bukan saya
sumber
Kamu benar. Awalnya pertanyaan itu tidak dirumuskan seperti itu, saya menghapus bagian dan artinya berubah. Sudah diperbaiki sekarang. Terima kasih!
2

Mengapa kendaraan harus tahu jika harus membayar? Bukan masalah Administrasi Publik?

Tidak. Seperti yang saya mengerti, seluruh aplikasi Anda adalah tentang perpajakan. Jadi kekhawatiran tentang apakah kendaraan harus dikenakan pajak tidak lagi menjadi perhatian Administrasi Publik maka hal lain di seluruh program. Tidak ada alasan untuk meletakkannya di tempat lain selain di sini.

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

Kedua cuplikan kode ini hanya berbeda dengan konstanta. Alih-alih menimpa seluruh fungsi HasToPay () menimpa int DaysBetweenPayments()fungsi. Sebut itu alih-alih menggunakan konstanta. Jika ini yang mereka benar-benar berbeda, saya akan meninggalkan kelas.

Saya juga menyarankan bahwa Sepeda Motor / Mobil harus menjadi properti kategori kendaraan daripada subclass. Itu memberi Anda lebih banyak fleksibilitas. Misalnya, membuatnya mudah untuk mengubah kategori sepeda motor atau mobil. Tentu saja sepeda tidak mungkin diubah menjadi mobil di kehidupan nyata. Tapi Anda tahu bahwa kendaraan akan dimasukkan salah dan perlu diubah nanti.

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?

Tidak juga. Objek yang dengan sengaja mengirimkan datanya ke objek lain sebagai parameter bukanlah masalah enkapsulasi yang besar. Perhatian sebenarnya adalah objek lain yang mencapai dalam objek ini untuk menarik data keluar atau memanipulasi data di dalam objek.

Winston Ewert
sumber
1

Anda mungkin berada di jalur yang benar. Masalahnya adalah, seperti yang Anda perhatikan, bahwa tidak ada anggota LastPaidTime di dalam Pembayaran Pajak . Itu persis di mana tempatnya, meskipun tidak perlu diungkapkan kepada publik sama sekali:

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

Setiap kali Anda menerima pembayaran, Anda membuat instance baru ITaxPaymentsesuai dengan kebijakan saat ini, yang dapat memiliki aturan yang sama sekali berbeda dari yang sebelumnya.

Groo
sumber
Masalahnya adalah bahwa pembayaran jatuh tempo tergantung pada terakhir kali pemilik membayar (kendaraan berbeda, tanggal jatuh tempo berbeda). Bagaimana ITaxPayment tahu yang terakhir kali dibayar kendaraan (atau pemilik) untuk melakukan perhitungan?
1

Saya setuju dengan jawaban @sebastiangeiger bahwa masalahnya sebenarnya tentang kepemilikan kendaraan dan bukan kendaraan. Saya akan menyarankan menggunakan jawabannya tetapi dengan beberapa perubahan. Saya akan membuat Ownershipkelas menjadi kelas generik:

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

Dan gunakan warisan untuk menentukan interval pembayaran untuk setiap jenis kendaraan. Saya juga menyarankan menggunakan kelas yang sangat diketik TimeSpandaripada bilangan bulat biasa:

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

Sekarang kode aktual Anda hanya perlu berurusan dengan satu kelas - 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);
    }
}
pengguna74130
sumber
0

Saya benci untuk membocorkannya kepada Anda, tetapi Anda memiliki domain anemia , yaitu logika bisnis di luar objek. Jika ini yang Anda maksud, tidak apa-apa, tetapi Anda harus membaca alasan untuk menghindarinya.

Cobalah untuk tidak memodelkan domain masalah, tetapi memodelkan solusinya. Itulah sebabnya Anda berakhir dengan hierarki warisan paralel dan kompleksitas lebih dari yang Anda butuhkan.

Sesuatu seperti ini adalah yang Anda butuhkan untuk contoh itu:

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

Jika Anda ingin mengikuti SOLID itu bagus, tetapi seperti yang dikatakan Paman Bob sendiri, itu hanya prinsip rekayasa . Mereka tidak dimaksudkan untuk diterapkan secara ketat, tetapi merupakan pedoman yang harus dipertimbangkan.

Garrett Hall
sumber
4
Saya pikir solusi Anda tidak terukur. Anda perlu menambahkan boolean baru untuk setiap jenis kendaraan yang Anda tambahkan. Itu pilihan yang buruk menurut saya.
Erik Funkenbusch
@Garrett Hall, saya suka Anda menghapus kelas Person dari "domain" saya. Saya tidak akan memiliki kelas itu di tempat pertama, sangat menyesal tentang itu. Tetapi isMotorBike tidak masuk akal. Mana yang akan menjadi implementasinya?
1
@DavidRobertJones: Saya setuju dengan Mystere, menambahkan isMotorBikemetode bukanlah pilihan desain OOP yang baik. Ini seperti mengganti polimorfisme dengan kode tipe (walaupun boolean).
Groo
@MystereMan, orang bisa dengan mudah menjatuhkan bools dan menggunakanenum Vehicles
radarbob
+1. Cobalah untuk tidak memodelkan domain masalah, tetapi memodelkan solusinya . Empuk . Kata menghafal. DAN komentar prinsip-prinsip. Saya melihat terlalu banyak upaya interpretasi literal yang ketat ; omong kosong.
radarbob
0

Saya pikir Anda benar bahwa periode pembayaran bukan properti dari Mobil / Motor yang sebenarnya. Tapi itu adalah atribut dari kelas kendaraan.

Meskipun Anda bisa pergi jalan memiliki if / select pada Jenis objek, ini tidak terlalu scalable. Jika Anda kemudian menambahkan truk dan Segway dan mendorong sepeda dan bus dan pesawat terbang (mungkin tampak tidak mungkin, tetapi Anda tidak pernah tahu dengan layanan publik) maka kondisinya menjadi lebih besar. Dan jika Anda ingin mengubah aturan untuk truk, Anda harus menemukannya di daftar itu.

Jadi mengapa tidak membuatnya, secara harfiah, atribut dan menggunakan refleksi untuk menariknya? Sesuatu seperti ini:

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

Anda juga bisa menggunakan variabel statis, jika itu lebih nyaman.

Kerugiannya adalah

  • bahwa itu akan sedikit lebih lambat, dan saya berasumsi Anda harus memproses banyak kendaraan. Jika itu adalah masalah maka saya mungkin mengambil pandangan yang lebih pragmatis dan tetap dengan properti abstrak atau pendekatan yang terhubung. (Meskipun, saya akan mempertimbangkan caching berdasarkan jenis juga.)

  • Anda harus memvalidasi saat startup bahwa setiap subkelas Kendaraan memiliki atribut IPaymentRequiredCalculator (atau mengizinkan default), karena Anda tidak ingin memproses 1.000 mobil, hanya macet karena Sepeda Motor tidak memiliki PaymentPeriod.

Sisi baiknya adalah jika Anda menemukan bulan kalender atau pembayaran tahunan, Anda bisa menulis kelas atribut baru. Ini adalah definisi utama dari OCP.

pdr
sumber
Masalah utama dengan ini adalah bahwa jika Anda ingin mengubah jumlah hari antara pembayaran untuk Kendaraan Anda perlu membangun kembali dan memindahkan, dan jika frekuensi pembayaran bervariasi berdasarkan pada properti entitas (misalnya ukuran mesin) itu akan menjadi sulit untuk mendapatkan perbaikan yang elegan untuk itu.
Ed James
@ Edwoodcock: Saya pikir masalah pertama adalah benar untuk sebagian besar solusi dan itu benar-benar tidak akan membuat saya khawatir. Jika mereka menginginkan tingkat fleksibilitas itu nanti, saya akan memberi mereka aplikasi DB. Pada poin kedua, saya sangat tidak setuju. Pada titik itu, Anda cukup menambahkan kendaraan sebagai argumen opsional ke HasToPay () dan mengabaikannya di tempat yang tidak Anda perlukan.
pdr
Saya kira itu tergantung pada rencana jangka panjang yang Anda miliki untuk aplikasi, ditambah persyaratan khusus dari klien, apakah layak memasukkan DB jika tidak ada (saya cenderung berasumsi bahwa hampir semua perangkat lunak adalah DB -bergerak, yang saya tahu tidak selalu terjadi). Namun, pada poin kedua, kualifikasi penting adalah elegan ; Saya tidak akan mengatakan bahwa menambahkan parameter tambahan ke metode pada atribut yang kemudian mengambil instance dari kelas yang melekat atribut adalah solusi yang sangat elegan. Tetapi, sekali lagi, itu tergantung pada kebutuhan klien Anda.
Ed James
@ Edwoodcock: Sebenarnya, saya hanya memikirkan hal ini dan argumen LastPaid sudah harus menjadi properti Vehicle. Dengan mengingat komentar Anda, mungkin perlu mengganti argumen itu sekarang, daripada nanti - akan diedit.
pdr