【发布时间】:2015-12-15 07:15:07
【问题描述】:
我正在尝试实现一个流畅的接口,让我指定一个带有列的表,其中表和列具有属性。我希望能够像这样初始化表类:
var t = TableBuilder.Create()
.WithName("test")
.WithColumn("col1").WithType("Int32")
.WithColumn("col2").WithType("String")
.WithColumn("col3");
我想出了一个可行的解决方案。我的问题是,可以简化实施还是应该采取完全不同的方法?我在类似的案例中看到了扩展方法的使用,但我不知道它们是否会帮助我或在这种情况下如何使用它们。
这是代码(下面有一些 cmets):
interface ITable
{
ITable WithName(string tableName);
ITableAndColum<ITable, IColumn> WithColumn(string columnName);
}
interface IColumn
{
IColumn WithType(string typeName);
}
interface ITableAndColum<TTable, TColumn>
where TTable : ITable
where TColumn : IColumn
{
ITableAndColum<ITable, IColumn> WithColumn(string columnName);
ITableAndColum<ITable, IColumn> WithType(string typeName);
}
class TableAndColumn : ITableAndColum<ITable, IColumn>
{
public ITable Table { get; set; }
public IColumn Column { get; set; }
public ITableAndColum<ITable, IColumn> WithColumn(string columnName)
{
return Table.WithColumn(columnName);
}
public ITableAndColum<ITable, IColumn> WithType(string typeName)
{
Column.WithType(typeName);
return this;
}
public override string ToString()
{
return Table.ToString();
}
}
class Column : IColumn
{
public string ColumnName { get; set; }
public string TypeName { get; set; }
public IColumn WithType(string typeName)
{
TypeName = typeName;
return this;
}
}
class TableBuilder : ITable
{
public string TableName { get; set; }
List<Column> Columns;
TableBuilder() { Columns = new List<Column>(); }
public static ITable Create() { return new TableBuilder(); }
public ITable WithName(string tableName)
{
TableName = tableName;
return this;
}
public ITableAndColum<ITable, IColumn> WithColumn(string columnName)
{
Column thisColumn = new Column() { ColumnName = columnName };
Columns.Add(thisColumn);
return new TableAndColumn() { Table = this, Column = thisColumn };
}
public override string ToString()
{
return TableName + "(" + string.Join(",", Columns.Select(c => (c.TypeName != null ? c.TypeName + " " : "") + c.ColumnName)) + ")";
}
}
class Example
{
public Example()
{
var t = TableBuilder.Create()
.WithName("test")
.WithColumn("col1").WithType("Int32")
.WithColumn("col2").WithType("String")
.WithColumn("col3");
var tdef = t.ToString();
}
}
有两个接口ITable 和IColumn 实现Table 和Colum。它们公开了特定于其类型的适当方法。第三个接口ITableAndColumn 结合了其他接口,它的实现TableAndColumn 持有对Table 实例和Column 实例的引用。该接口公开ITable 和IColumn 的选定方法并调用相应的实例。
这种方法复杂吗?扩展方法会以任何方式简单吗?
【问题讨论】:
-
出于好奇,您为什么反对使用传统风格设计您的 API?
-
对这个问题的回答会非常有偏见,但我的评论也是如此 - 作为个人喜好,我希望将
WithType方法称为AndType,这样你就会得到WithColumn("col1").AndType("Int32")我宁愿使用实际的Types 而不是字符串来定义类型.. -
我想让我的 API 更具可读性。当然,这只适用于代码的适当缩进和换行符,并且如果链不会变得太长,但我可以忍受这两个缺点。而且我知道我可以使用带有构造函数初始化程序和名称参数的更简单的方法链接。但我仍然想知道一些 api 是如何完成我在示例代码中说明的。
-
@MikeDinescu:关于 WithType/AndType,您是对的。我牢记这一点。您对传递给该方法的实际类型也是正确的。我想让上面的代码更简单一些。这就是我选择参数为字符串的原因。
-
@MikeDinescu:作为一个附带问题:与您命名 WithType 而不是 AndType 的意义相同,如果该方法适用于 Colum 的属性,您会命名 ITableAndColumn.IsNullable 而不是 ITableAndColumn.WhichIsNullable?
标签: c# generics fluent-interface