Interface Segregation Framework and Pattern

时间:2016-08-31 17:02:19

标签: c# interface single-responsibility-principle

I am writing an app that processes a bunch of ticker data from a page. The main class that I am working with is called Instrument, which is used to store all the relevant data pertaining to any instrument. The data is downloaded from a website, and parsed.

class Instrument
{
    string Ticker {get; set;}
    InstrumentType Type {get; set;}
    DateTime LastUpdate {get; set;}
}

My issue is that I am not sure how to properly structure the classes that deal with the parsing of the data. Not only do I need to parse data to fill in many different fields (Tickers, InstrumentType, Timestamps etc.), but because the data is pulled from a variety of sources, there is no one standard pattern that will handle all of the parsing. There are even some parsing methods that need to make use of lower level parsing methods (situations where I regex parse the stock/type/timestamp from a string, and then need to individually parse the group matches).

My initial attempt was to create one big class ParsingHandler that contained a bunch of methods to deal with every particular parsing nuance, and add that as a field to the Instrument class, but I found that many times, as the project evolved, I was forced to either add methods, or add parameters to adapt the class for new unforeseen situations.

class ParsingHandler
{
      string GetTicker(string haystack);
      InstrumentType GetType(string haystack);
      DateTime GetTimestamp(string haystack);
}

After trying to adapt a more interface-centric design methodology, I tried an alternate route and defined this interface:

interface IParser<outParam, inParam> 
{
      outParam Parse(inParam data);
}

And then using that interface I defined a bunch of parsing classes that deal with every particular parsing situation. For example:

class InstrumentTypeParser : IParser<InstrumentType, string> 
{
      InstrumentType Parse(string data);
}

class RegexMatchParser : IParser<Instrument, Match> where Instrument : class, new()
{
     public RegexMatchParser(
          IParser<string, string> tickerParser,
          IParser<InstrumentType, string> instrumentParser,
          IParser<DateTime, string> timestampParser)
    {
       // store into private fields
    } 

    Instrument Parser(Match haystack) 
    {
          var instrument = new Instrument();
          //parse everything
          return instrument;
    }
}

This seems to work fine but I am now in a situation were it seems like I have a ton of implementations that I will need to pass into class constructors. It seems to be dangerously close to being incomprehensible. My thoughts on dealing with it are to now define enums and dictionaries that will house all the particular parsing implementations but I am worried that it is incorrect, or that I am heading down the wrong path in general with this fine-grained approach. Is my methodology too segmented? Would it be better to have one main parsing class with a ton of methods like I originally had? Are there alternative approaches for this particular type of situation?

1 个答案:

答案 0 :(得分:1)

I wouldn't agree with attempt to make the parser so general, as IParser<TOut, TIn>. I mean, something like InstrumentParser looks to be quite sufficient to deal with instruments.

Anyway, as you are parsing different things, like dates from Match objects and similar, then you can apply one interesting technique that deals with generic arguments. Namely, you probably want to have no generic arguments in cases when you know what you are parsing (like string to Instrument - why generics there?). In that case you can define special interfaces and/or classes with reduced generic arguments list:

interface IStringParser<T>: IParser<T, string> { }

You will probably parse data from strings anyway. In that case, you can provide a general-purpose class which parses from Match objects:

class RegexParser: IStringParser<T>
{
    Regex regex;
    IParser<T, Match> parser;

    public RegexParser(Regex regex, IParser<T, Match> containedParser)
    {
        this.regex = regex;
        this.parser = containedParser;
    }
    ...
    T Parse(string data)
    {
        return parser.Parse(regex.Match(data));
    }
}

By repeatedly applying this technique, you can make your top-most consuming classes only depend on non-generic interfaces or interfaces with one generic member. Intermediate classes would wrap around more complicated (and more specific) implementations and it all becomes just a configuration issue.

The goal is always to go towards as simple consuming class as possible. Therefore, try to wrap specifics and hide them away from the consumer.