使用类型推断与流畅接口

4

我有一个类/接口层次结构。在接口方面,我有:

IQuery
  ISelect      (inherits IQuery)
  IUpdate      (inherits IQuery)
  etc

在类的层面上,我有:
QueryBase       (implements IQuery)
  SelectQuery   (implements ISelect)
  UpdateQuery   (implements IUpdate)
  etc

显然,例如,Update和Select类都共享一个WHERE子句,但只有Select具有GROUP BY功能。因此,如果正在创建Update查询,则流畅接口将不提供访问GROUP BY功能,但如果正在创建SelectQuery,则应该提供该功能。
例如,在流畅接口术语中:
  var/Dim select = New SelectQuery()        <- returns ISelect explicit
                          .AddColumn(....)  <- returns ISelect explicit
                          .AddWhere(....)   <- returns ISelect inferred
                          .AddGroupBy(....) <- returns ISelect explicit

  var/Dim update = New UpdateQuery()        <- returns IUpdate explicit
                          .AddSet(....)     <- returns IUpdate explicit
                          .AddWhere(....)   <- returns IUpdate inferred

我不确定如何实现AddWhere函数。

之前我在IQuery接口中声明了AddWhere函数:

Function AddWhere(ByVal condition As ICriterion) As IQuery

IQuery AddWhere(ICriterion condition)

但是因为它返回一个IQuery,我失去了类型推断的好处,所以一旦流畅的接口强制转换为IQuery,如果正在创建一个Select查询,我将不再拥有例如AddGroupBy方法的访问权限。

因此,我尝试使用泛型实现它作为扩展方法。

<Extension>
Public Function AddWhere(Of T As IQuery)(Byval this as T, Byval condition as Condition) as T
    this.SetWhere(condition)
    Return Me
End Function

public T AddWhere<T>(T @this, Condition condition) where T : IQuery
{
    @this.SetWhere(condition);
    return this;
}

通过对QueryBase进行内部友元(internal Friend)方法SetWhere的设置,使我能够更新WHERE子句。但是因为泛型受到IQuery的约束,它找不到SetWhere。然而,如果我将其约束为QueryBase,则编译器会抛出异常,说无法找到AddWhere方法。
我认为我还没有完全理解继承链或接口实现,以达到我想要的目标。
如果有人能建议我在扩展方法实现方面出了什么问题,或者如何更好地构建我的类/接口层次结构,我将不胜感激。
(希望我的表述清晰明了!)

1
我认为https://dev59.com/sXI-5IYBdhLWcg3wsKr9可能是有用的阅读材料,因为它讨论了您正在尝试的简化形式。它主要说虽然不可能,但有一些变通方法,包括使基类成为泛型。如果您满意代码的重构,我认为最后一部分(dtb的答案)可能会做到您想要的。 - Chris
Chris,我稍微尝试了一下。那个 q 只与具体类交互,我想知道如果我的情况还涉及接口层次结构是否会更加复杂。我尝试将 QueryBase 泛型化为 QueryBase(of T as IQuery),但是据我所知,这将意味着我的“SelectQuery 工厂”需要返回 SelectQuery 或 QueryBase(of ISelect)而不是 ISelect。同样,流畅的方法也需要返回具体类型。我之前返回纯接口是因为 ISP/易于模拟。我有什么遗漏吗? - Simon Woods
很抱歉,我不知道。我知道问题有点不同,如果要解决它,需要适当调整,但我以前没有尝试过,也没有时间自己去尝试。也许这些问题不能完美地帮助你解决问题,我只是抱着一丝希望。;-) - Chris
2个回答

1
Public Interface IQuery
    Function AddWhere() As IQuery
End Interface

Public Interface IUpdate : Inherits IQuery
    Overloads Function AddWhere() As IUpdate
End Interface

Public Interface ISelect : Inherits IQuery
    Overloads Function AddWhere() As ISelect
    Function AddGroupBy() As ISelect
End Interface

Public Class QueryBase : Implements IQuery
    Public Function AddWhere() As IQuery Implements IQuery.AddWhere
        ''...
        Return Me
    End Function
End Class

Public Class UpdateQuery : Inherits QueryBase : Implements IUpdate
    Public Shadows Function AddWhere() As IUpdate Implements IUpdate.AddWhere
        MyBase.AddWhere()
        Return Me
    End Function
End Class

Public Class SelectQuery : Inherits QueryBase : Implements ISelect
    Public Shadows Function AddWhere() As ISelect Implements ISelect.AddWhere
        MyBase.AddWhere()
        Return Me
    End Function
    Public Function AddGroupBy() As ISelect Implements ISelect.AddGroupBy
        ''...
        Return Me
    End Function
End Class

不用谢 - 我回答完这个问题才意识到它已经很老了。很高兴它仍然有用。 - Ian Horwill

0

也许你可以在你的继承体系中使用另一个接口,例如:

interface IQuery

interface IConditional : IQuery

interface ISelect : IConditional

interface IUpdate : IConditional

IConditional 接口可以在接口定义中直接添加 AddWhere 方法,也可以作为约束在 IConditional 类型上的扩展方法。


AddWhere方法会返回什么呢?在那种情况下,它仍然需要返回IQuery或IConditional,不是吗? - Chris
再次阅读您的问题,是否有任何原因要求在选择之前使用AddWhere而不是AddGroupBy?另外,您最终想要返回什么?对我来说,听起来您的变量将是IQuery类型,然后具有某种执行方法,是吗? - Peter Monks
我怀疑除了其他原因外,答案是否存在任何不应该先出现的理由?流畅的东西的重点在于,我相信方法返回的是它们正在运行的同一对象,因此如果您在ISelect上运行方法,则应该返回一个ISelect,而不是IQuery。如果您将AddWhere限制为最后一件事,那么有办法,但它不会真正流畅(例如,因为您将获得不同一组方法的访问权限)。我还应该注意,我不是原始帖子的作者,所以我的观点可能不是他们的观点。 :) - Chris
1
在我看来,我不明白为什么流畅的接口必须在整个链中返回完全相同的对象。我对流畅接口的理解是,方法/函数的命名和设计方式使您可以(几乎)以普通英语阅读它。以OrderBy LINQ方法为例-它传递了一个IEnumerable<T>但返回了一个IOrderedEnumerable<T>。只要您可以为下一个操作返回可用的内容,我就不明白那不是流畅的。 - Peter Monks
Peter/Chris...谢谢你们的想法。正如Peter在上面指出的那样,在我的原始实现中,AddGroupBy需要在AddWhere之前添加,这样才能像你建议的那样工作...但我觉得这破坏了我所追求的SQL隐喻。因此,我试图改进API。 - Simon Woods

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接