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;
                }   
            }   
        }
    }
  • 2
    My head hurts. . – Robert Harvey May 22 '18 at 19:39
  • @RobertHarvey because my code is that bad? – pitersmx May 22 '18 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. – Robert Harvey May 22 '18 at 20:21
  • One function with dozen of if's - not the best way to go. – pitersmx May 23 '18 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. – John Wu May 23 '18 at 19:59
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);
  • 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! – T. Sar May 23 '18 at 20:18
  • Hard to improve on a Harvey answer :) – John Wu May 23 '18 at 21:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.