これはプログラミング言語にとらわれない質問かもしれませんが、.NETエコシステムを対象とした回答に興味があります。
これがシナリオです:行政用のシンプルなコンソールアプリケーションを開発する必要があるとします。アプリケーションは、車両税に関するものです。これらの(のみ)次のビジネスルールがあります。
1.a)車両が自動車で、所有者が最後に税金を支払ったのが30日前であれば、所有者は再度支払う必要があります。
1.b)車両がバイクで、所有者が最後に税金を支払ったのが60日前であれば、所有者は再度支払う必要があります。
つまり、車がある場合は30日ごとに支払う必要があり、バイクがある場合は60日ごとに支払う必要があります。
システム内の各車両について、アプリケーションはそれらのルールをテストし、それらを満たさない車両(プレート番号と所有者情報)を印刷する必要があります。
私が欲しいのは:
2.a)SOLID原則(特にオープン/クローズド原則)に準拠します。
私が欲しくないのは(私が思うに):
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:オープン/クローズド原則が保証されています。自転車税が必要な場合は、Vehicleから継承するだけで、HasToPayメソッドをオーバーライドして完了です。単純な継承によって満たされるオープン/クローズド原則。
「問題」は次のとおりです。
3.a)車両が支払いが必要かどうかを知る必要があるのはなぜですか?PublicAdministrationの問題ではありませんか?自動車税の行政規則が変更された場合、なぜ自動車を変更する必要があるのですか?「それでは、なぜVehicleにHasToPayメソッドを配置するのですか?」と答えるべきだと思います。その答えは、PublicAdministration内で車両タイプ(typeof)をテストしたくないからです。より良い代替手段がありますか?
3.b)納税は結局車両の問題かもしれません。あるいは、最初の個人-車両-車-バイク-公共管理の設計がまったく間違っているか、哲学者とより良いビジネスアナリストが必要です。解決策としては、HasToPayメソッドを別のクラスに移動し、TaxPaymentと呼ぶことができます。次に、2つのTaxPayment派生クラスを作成します。CarTaxPaymentおよびMotorbikeTaxPayment。次に、(TaxPaymentタイプの)抽象的なPaymentプロパティをVehicleクラスに追加し、CarクラスとMotorbikeクラスから正しい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());
}
しかし、CarTaxPayment / MotorbikeTaxPayment / TaxPayment内にLastPaidTimeメンバーがないため、このコードはコンパイルされません。CarTaxPaymentとMotorbikeTaxPaymentは、ビジネスエンティティではなく、「アルゴリズムの実装」のように見え始めています。どういうわけか、これらの「アルゴリズム」にはLastPaidTime値が必要です。もちろん、この値をTaxPaymentのコンストラクターに渡すことはできますが、それは車両のカプセル化や責任に違反することになるでしょう。
3.c)Entity Framework ObjectContext(ドメインオブジェクト、Person、Vehicle、Car、Motorbike、PublicAdministrationを使用)が既にあるとします。PublicAdministration.GetAllVehiclesメソッドからObjectContext参照を取得し、機能を実装するにはどうしますか?
3.d)CarTaxPayment.HasToPayには同じObjectContext参照が必要ですが、MotorbikeTaxPayment.HasToPayには異なるものが必要な場合、「注入」を担当するのは誰ですか、またはそれらの参照を支払いクラスにどのように渡しますか?この場合、貧弱なドメインを回避する(したがってサービスオブジェクトを回避する)ことで、私たちは災害に陥ることはありませんか?
この単純なシナリオの設計上の選択は何ですか?明らかに、私が示した例は「複雑すぎて」簡単なタスクではありませんが、同時に、SOLID原則を順守し、(破壊するように委託された)貧血領域を防ぐことも考えています。