Implementing interfaces within interfaces

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?

Answers


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'.


Need Your Help

Create a column with primary key using migrations

yii database-migration yii-migrations

I am using migrations in Yii to create a new column with. My code works fine however, I am unsure how to set a primary key?