이것은 프로그래밍 언어에 무관심한 질문 일 수 있지만 .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) 왜 차량이 지불해야하는지 알아야합니까? 공공 행정 문제가 아닙니까? 정부가 자동차 세금을 변경해야하는 경우 자동차를 변경해야하는 이유는 무엇입니까? "왜 차량에 HasToPay 메서드를 넣었을까요?" 더 나은 대안이 있습니까?
3.b) 어쩌면 세금 납부는 결국 차량 문제 일 수도 있고, 또는 나의 초기 개인 차량-자동차-자전거-공공 행정 설계가 잘못되어 있거나 철학자와 더 나은 비즈니스 분석가가 필요할 수도 있습니다. 해결책은 다음과 같습니다. HasToPay 메소드를 다른 클래스로 옮기면 TaxPayment라고 할 수 있습니다. 그런 다음 TaxPayment 파생 클래스를 두 개 만듭니다. CarTaxPayment 및 오토바이 TaxPayment. 그런 다음 Vehicle 클래스에 추상 Payment 속성 (TaxPayment 유형)을 추가하고 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) Entity Framework ObjectContext (개인, 차량, 자동차, 오토바이, 공공 행정)를 사용하는 Entity Framework ObjectContext가 이미 있다고 가정합니다. PublicAdministration.GetAllVehicles 메소드에서 ObjectContext 참조를 가져 와서 기능을 구현하려면 어떻게해야합니까?
3.d) CarTaxPayment.HasToPay에 대해 동일한 ObjectContext 참조가 필요하지만 "주입"을 책임지는 MotorbikeTaxPayment.HasToPay에 대해 다른 ObjectContext 참조가 필요한 경우 또는 해당 참조를 지불 클래스에 전달하는 방법은 무엇입니까? 이 경우 빈혈 도메인을 피하고 서비스 객체를 피하면 재앙으로 이어지지 않습니까?
이 간단한 시나리오를위한 디자인 선택은 무엇입니까? 내가 보여준 예제는 쉬운 작업을하기에는 너무 복잡하지만 동시에 아이디어는 SOLID 원칙을 준수하고 빈혈 영역 (파괴 명령)을 방지하는 것입니다.