1

I'm writing some code for importing files which will import either delimited or fixed width files based on a template that describes the file layout.

I've created an interface IFileTemplate:

public interface IFileTemplate
{
   string Name { get; set; }
   bool IgnoreEmptyLines { get; set; }
}

which is used by a DelimitedFileTemplate class and a FixedWidthFileTemplate class.

I also have an interface for specifying each of the columns that make up a template:

public interface IFileTemplateColumn
{
   int ID { get; set; }
   string Name { get; set; }
   bool Ignore { get; set; }
}

This interface is then used by a DelimitedTemplateColumn class and a FixedWidthTemplateColumn class.

As both the DelimitedFileTemplate and FixedWidthFileTemplate classes will have a list of columns I've made the list a member of the IFileTemplate column:

List<IFileTemplateColumn> Fields { get; set; }

My problem is when I've come to implement the list in the DelimitedFileTemplate and FixedWidthFileTemplate classes, for example:

public class FixedWidthFileTemplate : IFileTemplate
{
   public int ID { get; set; }
   public string Name { get; set; }
   public List<FixedWidthFileTemplateColumn> Fields { get; set; }
}

If I try and implement List<IFileTemplateColumn> with List<DelimitedFileTemplateColumn> or List<FixedWidthFileTemplateColumn> then the compiler complains that they don't match List<IFileTemplateColumn>.

I can understand this but it seems wrong not to have the column list in the ITemplateInterface. The only get around I can think of is to have the Delimited and FixedWidth classes use List<IFileTemplateColumn> and have the property getter cast the list to the delimited or fixed width column list but there seems a bit of code smell to that. Can anyone suggest a better way for doing this?

11
  • It would be better if you add code for classes where you have implemented interfaces. It will be more readable. Commented Jan 31, 2014 at 12:05
  • @FaisalHafeez Good suggestion, now updated. Commented Jan 31, 2014 at 12:10
  • I would step back and ask why you have interfaces at all. Will there be three classes that implement each interface but have no common base class other than object? Commented Feb 3, 2014 at 15:11
  • @EricLippert I initially picked interfaces as the code was going to be used by a couple of other projects and thought interfaces would be more appropriate if used across boundaries. As the interfaces aren't going to be used outside of this code (calling code will only used the classes implementing the interface) I may reconsider this. Thanks for your suggestion. Commented Feb 3, 2014 at 15:20
  • 1
    Well, strings are used across many projects but there is no IString interface. Integers are used across many projects but there is no IInteger interface. Use interfaces when you have many unrelated classes that provide similar functionality; IEnumerable for example. There are many ways to implement a sequence that have nothing to do with each other. A good guidelines is: if you're not going to have three or more implementations, don't make an interface. Commented Feb 3, 2014 at 16:20

1 Answer 1

1

A suitable and not smelly solution to this design problem are generics:

interface IFileTemplate<T> where T : IFileTemplateColumn
{
    List<T> Fields { get; set; }
}

DelimitedFileTemplate implements IFileTemplate<DelimitedFileTemplateColumn> and so on.

Perhaps all the differences between the file templates could be sensibly defined by IFileTemplateColumn only and you could simplify things with FileTemplate<IFileTemplateColumn> insted of one FileTemplate class per one FileTemplateColumn class relation.

Update

As for the factory method: IFileTemplate<IFileTemplateColumn> Create: if the consumers of this method are supposed to be able to access the list of columns, the method signature will have to contain the concrete ColumnTemplate. For example:

DelimitedFileTemplate Create

or

interface IFactory<T> where T : IFileTemplateColumn
{
    IFileTemplate<T> Create();
}

class DelimitedFactory : IFactory<DelimitedFileTemplateColumn>
{
    IFileTemplate<DelimitedFileTemplateColumn> Create() 
    {
        return new DelimitedFileTemplate();
    }
}

If the consumers of the method won't be interested in the list, introduce a more general interface (much like IEnumerable<T> : IEnumerable):

interface IFileTemplate { ... }
interface IFileTemplate<T> : IFileTemplate where T : IFileTemplateColumn
{
    List<IFileTemplateColumn> Columns { get; set; }
}

Then your IFileTemplate Create() method could return any of the concrete FileTemplate regardless of the column.

I've worked with this kind of generics usage and they might tend to propagate (in this example Column hierarchy will be duplicated in FileTemplate hierarchy and might be duplicated in the factory hierarchy). Sometimes this reveals some flaws in the design. If you were able to sensibly cut the IFileTemplate hierarchy to one base parametrized FileTemplate class, this was certainly the way to go. This is how I often use this: define the smallest parts, if the hierarchy tends to duplicate, some parts of the algorithms can be perhaps moved to the 'smallest-parts-classes'.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for your answer, I found it easier to go with your second suggestion when implementing this solution with MVVM. Returning a list of all the templates would have been too difficult to manage if the list were to contain different types of template.
@GrandMasterFlush glad you were able to work this out on your own. i finally had some time to get back to this - see update, i had similar problems.
Thanks for the update, that's given me a few more things to look into. Cheers.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.