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