Search code examples
c#.netrefactoringsolid-principlesopen-closed-principle

Open-closed Principle - How to refactor with arguments to providers


I'm working on some legacy dotnet framework 4.7 code which seems to break the open-closed principle. The report classes do pretty much the same thing: Gets some items from a database, runs some transformation and returns them in the GetItems method. I was asked to add another report, but this would mean changing the ReportManager class which just grows and grows. The reports need different parameters, which makes it difficult for me to see how I can move forward. How can I refactor this so it doesn't break the O in SOLID?

UPDATED

Added missing call to GetItems() in both reports, as well as calls to _repository.


public interface IReport
{
    string GetItems();
}

public class StoreItemsReport : IReport
{
    private readonly int[] _numbers;
    private readonly IRepository _repository;
    public StoreItemsReport(IRepository repo, int[] numbers)
    {
        this._repository = repo;
        this._numbers = numbers;
    }
    public string GetItems()
    {
        return _repository.GetStoreItems(numbers);
    }
}

public class OnlineItemsReport : IReport
{
    private readonly string _account;
    private readonly IRepository _repository;
    public OnlineItemsReport(IRepository repo, string account)
    {
        this._repository = repo;
        this._account = account;
    }
    public string GetItems()
    {
        return _repository.GetOnlineItems(account);
    }
}

public class ReportManager
{
    private readonly IRepository repository;
    public ReportManager(IRepository repository)
    {
        this.repository = repository;
    }
    public void HandleSupplierItems(int[] numbers)
    {
        var items = new StoreItemsReport(repository, numbers).GetItems();
        Utils.SendData("storeitems-url", items);
        Emailer.SendReport(items);
    }

    public void HandleStoreItems(string account)
    {
        var items = new OnlineItemsReport(repository, account).GetItems();
        Utils.SendData("onlineitems-url", items);
        Emailer.SendReport(items);
    }

    // and so on
}

// I would like to do something like this, but how do I add arguments to the providers
var provider = providerFactory.GetProvider("Suppliers");
var items = provider.GetItems();
Utils.SendData("url", items);
Emailer.SendReport(items);


Solution

  • You could have your ReportManager accept a factory method:

    public class ReportManager
    {
        private readonly IRepository repository;
        public ReportManager(IRepository repository)
        {
            this.repository = repository;
        }
    
        public void HandleItems(Func<IRepository, IReport> factory, string url)
        {
            var items = factory(repository).GetItems();
            Utils.SendData(url, items);
            Emailer.SendReport(items);
        }
    }
    

    Which could be used like this:

    var numbers = new[] { 1, 2, 3 };
    reportManager.HandleItems(repo => new StoreItemsReport(repo, numbers), "storeitems-url");