I am trying to better understand the use of the factory method pattern and adhering to SOLID principles and I am not sure if my implementation is correct for the following reasons:
- The dependencies my
IBankAccountAddOnServiceimplementations need (instantiated in my factory) the larger myBankAccountAddOnFactoryconstructor is going to get. Shouldn't eachIBankAccountAddOnServicebe responsible for its own dependencies via DI? - In the constructor params for each
IBankAccountAddOnServiceimplementation not only do they contain their dependencies but also the concrete type ofIBankAccountAddOnspecific to that service (e.g.CreditCardAddOnforCreditCardAddOnService). This feels wrong and is why I can't use DI to set them for each service. How could I get theBuildAddOnmethod to take in the relevant concreteIBankAccountAddOninstead? - Does the
switchstatement violate theOpen Closed Principleor is it ok within a factory? If there were many more bank addons in future, the switch statement may become very large?
IBankAccountAddOn and it's implementations (there may be many of these)
public interface IBankAccountAddOn
{
int Id { get; }
}
public class CreditCardAddOn : IBankAccountAddOn
{
public int Id { get; }
public int CustomerId { get; set; }
public double Limit { get; set; }
public double BalanceTransfer { get; set; }
}
public class TravelInsuranceAddOn : IBankAccountAddOn
{
public int Id { get; }
public int CustomerId { get; set; }
public DateTime Start { get; set; }
public int? MonthsDuration { get; set; }
}
IBankAccountAddOnService that my factory creates dependent upon IBankAccountAddOn
Note - The IExternal... interfaces are from 3rd party libraries.
public interface IBankAccountAddOnResult
{
bool Success { get; set; }
List<string> Errors { get; set; }
}
public class BankAccountAddOnResult : IBankAccountAddOnResult
{
public bool Success { get; set; }
public List<string> Errors { get; set; }
}
public interface IBankAccountAddOnService
{
IBankAccountAddOnResult BuildAddOn();
}
public class CreditCardAddOnService : IBankAccountAddOnService
{
private readonly IExternalCreditCardService _creditCardService;
private readonly IRepository _repository;
private readonly CreditCardAddOn _creditCardAddOn;
public CreditCardAddOnService(IExternalCreditCardService creditCardService, IRepository repository, CreditCardAddOn creditCardAddOn)
{
_creditCardService = creditCardService;
_repository = repository;
_creditCardAddOn = creditCardAddOn;
}
public IBankAccountAddOnResult BuildAddOn()
{
var customerDetails = _repository.GetCustomer(_creditCardAddOn.CustomerId);
if (!customerDetails.CanApplyCreditCards)
{
return new BankAccountAddOnResult
{
Success = false,
Errors = new List<string>{
"Customer cannot apply for credit cards"
}
};
}
var result = _creditCardService.Apply(_creditCardAddOn);
return result;
}
}
public class TravelInsuranceAddOnService : IBankAccountAddOnService
{
private readonly IExternalTravelInsuranceService _travelInsuranceService;
private readonly TravelInsuranceAddOn _travelInsuranceAddOn;
public TravelInsuranceAddOnService(IExternalTravelInsuranceService travelInsuranceService, TravelInsuranceAddOn travelInsurance)
{
_travelInsuranceService = travelInsuranceService;
_travelInsuranceAddOn = travelInsurance;
}
public IBankAccountAddOnResult BuildAddOn()
{
var result = _travelInsuranceService.Apply(_travelInsuranceAddOn.CustomerId, _travelInsuranceAddOn.MonthsDuration, _travelInsuranceAddOn.Start);
return result;
}
}
Factory implementation
public interface IBankAccountAddOnFactory
{
IBankAccountAddOnService Create(IBankAccountAddOn addOn);
}
public class BankAccountAddOnFactory : IBankAccountAddOnFactory
{
private readonly IExternalCreditCardService _creditCardService;
private readonly IExternalTravelInsuranceService _travelInsuranceService;
private readonly IRepository _repository;
public BankAccountAddOnFactory(
IExternalCreditCardService creditCardService,
IExternalTravelInsuranceService travelInsuranceService,
IRepository repository
)
{
_creditCardService = creditCardService;
_travelInsuranceService = travelInsuranceService;
_repository = repository;
}
public IBankAccountAddOnService Create(IBankAccountAddOn addOn)
{
switch (addOn)
{
case CreditCardAddOn creditCard:
return new CreditCardAddOnService(_creditCardService, _repository, creditCard);
case TravelInsuranceAddOn travelInsurance:
return new TravelInsuranceAddOnService(_travelInsuranceService, travelInsurance);
//Many other addon cases
default:
throw new ArgumentOutOfRangeException();
}
}
}
Service that creates add ons for customers
public class BankAccountAddOnService
{
private IBankAccountAddOnFactory _bankAddOnFactory;
public BankAccountAddOnService(IBankAccountAddOnFactory bankAddOnFactory)
{
_bankAddOnFactory = bankAddOnFactory;
}
public IBankAccountAddOnResult Apply(IBankAccountAddOn addOn)
{
var applyService = _bankAddOnFactory.Create(addOn);
var response = applyService.BuildAddOn();
//Do something with response
return response;
}
}