尽管这可能是与编程语言无关的问题,但我对针对.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)为什么车辆必须知道是否必须付款?这不是公共行政问题吗?如果公共管理部门改变了汽车税,为什么还要改变汽车?我认为您应该问:“那为什么要在Vehicle中放入HasToPay方法?”,答案是:因为我不想在PublicAdministration中测试车辆类型(typeof)。您看到更好的选择了吗?
3.b)毕竟,纳税可能是与车辆有关的问题,或者我最初的Person-Vehicle-Car-Motorbike-PublicAdministration设计完全是错误的,或者我需要一个哲学家和一个更好的业务分析师。一个解决方案可能是:将HasToPay方法移至另一个类,我们可以将其称为TaxPayment。然后,我们创建两个TaxPayment派生类;CarTaxPayment和MotorbikeTaxPayment。然后,将一个抽象的Payment属性(类型为TaxPayment)添加到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的构造函数,但这会违反车辆封装或职责,或者违反那些OOP宣传人员的说法,不是吗?
3.c)假设我们已经有一个实体框架ObjectContext(使用我们的域对象; Person,Vehicle,Car,Motorbike,PublicAdministration)。您将如何从PublicAdministration.GetAllVehicles方法获取ObjectContext引用,从而实现功能性?
3.d)如果对于CarTaxPayment.HasToPay我们还需要相同的ObjectContext引用,但对于负责“注入”的MotorbikeTaxPayment.HasToPay也需要一个不同的ObjectContext引用,或者您将如何将这些引用传递给支付类?在这种情况下,避免出现贫乏的领域(从而避免出现服务对象),是不是会导致我们灾难?
在这种简单情况下,您有哪些设计选择?显然,我展示的示例对于完成一项简单的任务而言“太复杂了”,但与此同时,其思想是遵守SOLID原理并防止贫血域(已委托它们销毁)。