0

I have a FtpServerDataSeriesProvider that is used to provide data series from Ftp server files:

public interface class FtpServerDataSeriesProvider
{
    IEnumerable<DataSeries> GetDataSeries();
}

It has several concrete implementations that are specific to the company that owns the Ftp Server, like:

public class Company1FtpServerDataSeriesProvider : FtpServerDataSeriesProvider
{
    public IEnumerable<DataSeries> GetDataSeries()
    {
        // code to extract data series from ftp server files
    }
}

public class Company2FtpServerDataSeriesProvider : FtpServerDataSeriesProvider
{
    public IEnumerable<DataSeries> GetDataSeries()
    {
        // code to extract data series from ftp server files    
    }
}

The data series extraction process itself is handled by a FileDataMiner's which are used to extract data from specific file types content:

public interface IFileDataSeriesMiner
{
    IEnumerable<DataSeries> GetDataSeries(byte [] fileContent);
} 

GetDataSeries() method parameter is a raw bytes array that presents the file content, inside this method it would be encoded / turn into MemoryStream depending on the targer file type.

// To get data from CSV-based file
public abstract CsvFileDataSeriesMiner : IFileDataMiner
{
    public abstract IEnumerable<DataSeries> GetDataSeries(byte [] fileContent);
    // here might be some protected csv-related helper methods
}

// To get data from Xls-based file
public abstract XlsFileDataSeriesMiner : IFileDataMiner
{
    public abstract IEnumerable<DataSeries> GetDataSeries(byte [] fileContent);
    // here might be some protected xls-related helper methods
}

Then, there are also some concrete classes that represent different formats of input file data (each company stores data in different format):

public class Company1CsvFileDataSeriesMiner : CsvFileDataSeriesMiner
{
    public IEnumerable<DataSeries> GetDataSeries(byte [] fileContent)
    {
        // do actual stuff
    }       
}

public class Company2CsvFileDataSeriesMiner : CsvFileDataSeriesMiner
{
    public IEnumerable<DataSeries> GetDataSeries(byte [] fileContent)
    {
        // do actual stuff
    }       
}

public class Company1XlsFileDataSeriesMiner : XlsFileDataSeriesMiner
{
    public IEnumerable<DataSeries> GetDataSeries(byte [] fileContent)
    {
        // do actual stuff
    }       
}

public class Company2XlsFileDataSeriesMiner : XlsFileDataSeriesMiner
{
    public IEnumerable<DataSeries> GetDataSeries(byte [] fileContent)
    {
        // do actual stuff
    }       
}

Now, in order to able FtpServerDataSeriesProvider to extract data from specific file format content, I need to pass / inject IFileDataSeriesMiner somehow to the FtpServerDataSeriesProvider concrete constructors.

But, it might be that on FtpServer there are Csv and Xls (and other) files present, so I need to be able to select proper IFileDataSeriesMiner dynamically within FtpServerDataSeriesProvider code depending on the file format being currently processed.

So, my questions are:

  1. Should I inject a factory of IFileDataSeriesMiner into Company1FtpServerDataSeriesProvider / Company2FtpServerDataSeriesProvider constructors and then decide which miner object I need basing on e.g. file extension?

Miner's factory for Company1 would look like this:

    public interface IFileDataSeriesMinerFactory
    {
        IFileDataSeriesMiner CreateMiner(string fileName); 
    }

    public class Company1FileDataSeriesMinerFactory : IFileDataSeriesMinerFactory
    {
        // basing on file extension suitable Miner object is created
        public static IFileDataSeriesMiner CreateMiner(string fileName); 
    }

    public class Company1FtpServerDataSeriesProvider : FtpServerDataSeriesProvider
    {
        private IFileDataSeriesMinerFactory minerFactory;

        public Company1FtpServerDataSeriesProvider(IFileDataSeriesMinerFactory minerFactory)
        {
            this.minerFactory = minerFactory;
        }

        // code ommited
    }
  1. When there are several files that needs to be processed within server I would need to call the Miner's factory for every file that need processing, so for each of files new Miner object would be created - is that ok?

Simplified code for Company1FtpServerDataSeriesProvider:

    public class Company1FtpServerDataSeriesProvider : FtpServerDataSeriesProvider
    {
        private IFileDataSeriesMinerFactory minerFactory;

        public Company1FtpServerDataSeriesProvider(IFileDataSeriesMinerFactory minerFactory)
        {
            this.minerFactory = minerFactory;
        }

        public IEnumerable<DataSeries> GetDataSeries()
        {
            var ftpServerFilesToRead = ftpClient.GetFiles();

            foreach (string filepath in ftpServerFilesToRead)
            {
                byte[] fileBytes = ftpRequest.DownloadData(filepath); // Read file bytes
                var miner = minerFactory.CreateMiner(filename); // Create miner for current file to extract data series

                foreach (var dataSeries in miner.GetDataSeries(fileBytes))
                {
                    yield return dataSeries;
                }   
            }   
        }
    }
9
  • 2
    My head hurts. . Commented May 22, 2018 at 19:39
  • @RobertHarvey because my code is that bad? Commented May 22, 2018 at 20:11
  • 2
    It just seems like an awful lot of ceremony for something that could be solved with a simple first-class function. Commented May 22, 2018 at 20:21
  • One function with dozen of if's - not the best way to go. Commented May 23, 2018 at 7:41
  • 1
    I'm curious, what is the reasoning for having a separate class for each company that you do business with? That obviously won't scale if your business takes off. Commented May 23, 2018 at 19:59

1 Answer 1

3

Here's how you might solve this Strategy problem in languages that include support for first-class functions, including C#. It will eliminate a lot of the ceremony.

// This is just a stub class to demonstrate the use of a first-class function 
// to implement your Strategy Pattern.
public class UniversalDataMiner : IFileDataMiner
{
    Func<IFileDataSeriesMinerFactory, IEnumerable<DataSeries>> _getDataSeries;

    public UniversalDataMiner(Func<IFileDataSeriesMinerFactory, IEnumerable<DataSeries>> getDataSeries)
    {
        _getDataSeries = getDataSeries;
    }

    #region IFileDataMiner implementation
    IEnumerable<DataSeries> GetDataSeries()
    {
        return _getDataSeries();
    }
    #endregion
}

// This is the function that you will pass into the class above to implement
// the GetDataSeries functionality using FTP.
public IEnumerable<DataSeries> GetDataSeriesUsingFtp(IFileDataSeriesMinerFactory minerFactory)
{
    var ftpServerFilesToRead = ftpClient.GetFiles();

    foreach (string filepath in ftpServerFilesToRead)
    {
        byte[] fileBytes = ftpRequest.DownloadData(filepath); // Read file bytes
        var miner = minerFactory.CreateMiner(filename); // Create miner for current file to extract data series

        foreach (var dataSeries in miner.GetDataSeries(fileBytes))
        {
            yield return dataSeries;
        }   
    }   
}

// And this is how you set it up and use it.
IFileDataMiner miner = new UniversalDataMiner(GetDataSeriesUsingFtp);
IEnumerable<DataSeries> result = miner.GetDataSeries(myMinerFactory);
2
  • After reading the answer I was ready to type a similar answer to yours, but since you already did it I threw in my upvote. Good answer and good use of first-class functions! Commented May 23, 2018 at 20:18
  • Hard to improve on a Harvey answer :) Commented May 23, 2018 at 21:04

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.