vendredi 13 septembre 2019

Implementation of factory method pattern with dependencies

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:

  1. The dependencies my IBankAccountAddOnService implementations need (instantiated in my factory) the larger my BankAccountAddOnFactory constructor is going to get. Shouldn't each IBankAccountAddOnService be responsible for its own dependencies via DI?
  2. In the constructor params for each IBankAccountAddOnService implementation not only do they contain their dependencies but also the concrete type of IBankAccountAddOn specific to that service (e.g. CreditCardAddOn for CreditCardAddOnService). This feels wrong and is why I can't use DI to set them for each service. How could I get the BuildAddOn method to take in the relevant concrete IBankAccountAddOn instead?
  3. Does the switch statement violate the Open Closed Principle or 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;
    }
}

Aucun commentaire:

Enregistrer un commentaire