Domain object "Contragent"
Let's say I have an hierarchy of classes:
public class BaseContragent { public int Id { get; set; } } public class PersonContragent : BaseContragent { public string FirstName { get; set; } public string LastName { get; set; } } public class CompanyContragent : BaseContragent { public string CompanyName { get; set; } } public class BankCompanyContragent : CompanyContragent { public string BankBic { get; set; } } public class LocalBankCompanyContragent : BankCompanyContragent { public string NationalBankIdentificator { get; set; } } Actually, this hierarchy has many classes, it has much of business logic, and all fields are taken from business domain logic, so I can't change it just because of inconvenient external service.
Contragent - PersonContragent -- PersonWithoutCitizenshipContragent -- CitizenshipContragent --- LocalCitizenshipContragent --- ForeignCitizenshipContragnet - CompanyContragent -- BankCompanyContragent --- LocalBankCompanyContragent etc. Now, external system sends requests to create object
I have a request to create an object from external system, which looks like this:
public class CreateRequest { public int Id { get; set; } public string Name { get; set; } // default for base, value for subclasses public int Type { get; set; } // 0 - for Base, 1 for Person, 2 for Company // many other fields belonging to different types } There is only one type of create request for all types of Contragent, and which one should I use depends on Type value.
In order to isolate my system from this strange external system I have implemented some sort of anti-corruption layer with factories which create objects of this hierarchy:
public abstract class BaseContragentFactory { protected abstract Contragent Create(); protected abstract Fill(Contragent ca, CreateRequest createRequest); public Base CreateAndFill(CreateRequest createRequest) { var ca = Create(); Fill(ca, createRequest); return ca; } } public class SimpleContragentFactory : BaseContragentFactory { protected virtual Contragent Create() { return new Contragent(); } protected virtual void Fill(Contragent ca, CreateRequest createRequest) { ca.Id = createRequest.Id; } } public class PersonContragentFactory : SimpleContragentFactory { protected override Contragent Create() { return new PersonContragent(); } protected override void Fill(Contragent ca, CreateRequest createRequest) { var pca = ca as PersonContragent; if (pca == null) throw new InvalidOperationException("..."); base.Fill(pca, createRequest); string[] nameParts = createRequest.Name.Split(";"); // Firstname;Lastname pca.FirstName = nameParts[0]; pca.LastName = nameParts[1]; } } public class CompanyContragentFactory : SimpleContragentFactory { protected override Contragent Create() { return new CompanyContragent(); } protected override void Fill(Contragent ca, CreateRequest createRequest) { var bca = ca as CompanyContragent; if (bca == null) throw new InvalidOperationException("..."); base.Fill(bca, createRequest); bca.CompanyName = createRequest.Name; } } I have such factories for almost every type of Contragent.
public void Create(CreateRequest request) { BaseContragentFactory factory; switch (request.Type) { case 0: factory = new SimpleContragentFactory(); break; case 1: factory = new PersonContragentFactory(); break; case 2: factory = new CompanyContragentFactory(); break; default: throw new InvalidOperationException("..."); } Contragent ca = factory.CreateAndFill(request); } However, I don't like that this codes has casts, checks and that type-safety is only maintained by a developer.
So, I have two question about improving code type-safety and readability:
Question 1: how to combine Create and Fill methods? How to make this class have only one method. The problem that I need to be able to create document only once at the top-inherited class and then call base fill methods.
Question 2: is there any way to utilize generics to make these factories more type-safe and accept generic-typed values? May be, s1omething like this:
public abstract class BaseContragentFactory<T> where TContragent : Contragent, new() { protected TContragent Create() { return new TContragent(); } // ...