9

.NET 中扩展方法的一个有趣方面是您可以将它们应用于接口。对我来说,我可以在接口附近定义功能而不定义使程序集混乱的抽象类似乎很好。

我知道抽象类并没有过时或任何东西,但是您对在代码中使用这种副作用有何感想?

例子:

public static class IUserExtensions
{
    public static bool IsCurrentUser(this IUser user)
    {
        return (HttpContext.Current.User != null &&
                HttpContext.Current.User.Identity.Name == user.ID.ToString());
    }
}

public interface IUser {
    int ID { get; set; }
}
4

11 回答 11

9

扩展方法可以让您专注于抽象类实际上应该做什么。在抽象类中实现“实用程序”代码是一种诱惑,因为即使它可能不是逻辑继承树的一部分,实现者也会使用它。扩展方法让您可以将这些实用方法附加到接口,而不会弄乱抽象基类。

编辑

具体来说,我会应用这些准则。

遗产

  • 如果行为是基类的逻辑行为的一部分,请使用继承
  • 如果行为是横切的(适用于对象层次结构之外的事物),则不要使用继承。这些将需要复制。

实用程序类

  • 如果行为在逻辑上不属于它所作用的类,请使用实用程序类(静态类)
  • 如果实用程序类修改了它所作用的对象的内部状态,则不要使用它们。状态修改应该只保留对象实现层次结构。

扩展方法

  • 当扩展方法产生较少摩擦时,请使用与实用程序类相同的决定。如果采用扩展方法感觉不太自然,请不要这样做。
  • 请使用扩展方法将实用程序添加到您无法控制的类(如字符串)。
  • 当行为被它所扩展的类使用时,不要使用扩展方法。虽然可以做到,但感觉做作
  • 不要仅仅因为可以就使用扩展方法。事实上,除非你必须这样做,否则不要偏离良好的老式 OOP 。然而,当你不得不做的时候,扩展方法是一个相对无用且无害的决定。

编辑 2

想到另一个 DO 扩展方法

请使用扩展方法为某些实现提供自然语言(内部 DSL)。这是一个愚蠢的例子。

int age = getAge();
if (age.IsDraftAge() && !age.IsLegalDrinkingAge())
{
    Console.WriteLine(@"You cannot drink until your birthdate on {0}.
Join the army instead.",
                      age.GetYearWhenGettingDrunkIsOk());
}
于 2009-03-16T20:03:39.320 回答
4

我已经多次使用这个功能——事实上,这个功能是专门为装饰接口而不修改它们而创建的,因此是 LINQ。LINQ 的所有优点(至少对对象而言)都基于对 IEnumerable<T> 的扩展方法。

但是,我不认为扩展方法会在您需要时以任何方式替换抽象基类。扩展方法不能访问扩展类的私有状态——扩展方法看起来像实例,但仍然是静态的。

扩展方法和抽象基类解决了两种不同类型的问题,使用这两种范式的选择不应相互依赖。

因此,我对您的标题问题的回答是:不,扩展方法的存在不会降低抽象基类的吸引力。如果有人认为扩展方法的存在降低了抽象基类的吸引力,那么一开始就没有正确使用抽象基类。

于 2009-03-16T19:53:29.143 回答
3

不,它会减少抽象类的一些误用。

抽象类应该包含子类可以覆盖的默认实现/行为。如果考虑得当,这意味着您可以只覆盖该类的一个行为,并使用其余的默认值。扩展方法提供了与此完全不同的东西。

于 2009-03-16T20:00:47.207 回答
2

我确实喜欢扩展方法,因为它使我能够定义IList<T>我一直缺少的 Find(Predicate)、Remove(Predicate) 等。我认为您不能说它们是抽象类的替代品。

我还定义了扩展方法来添加通用行为。我喜欢我在对象或 ToXml 上的 ToJson 扩展方法,非常方便。

我对您的示例的唯一问题是我总是将 IsCurrentUser 视为属性而不是方法,而且我们没有扩展属性。但我在吹毛求疵

它们完全不相关。当我想对其他人将实现或继承的行为进行建模时,我会使用抽象类。

于 2009-03-16T19:53:24.697 回答
1

不,抽象类的吸引力也不小。抽象类为对象层次结构提供了适当的范例。

扩展方法只有在您需要将实例方法添加到已经存在的类但不能直接修改代码时才真正有用。

于 2009-03-16T19:52:54.510 回答
1

扩展方法很好,但您无法将它们或它们的用法与抽象类进行比较。不要忘记封装是什么,它是 OOP 的支柱之一......

于 2009-03-16T19:59:05.650 回答
0

我认为这取决于您的使用-

如果您主要关心的是重构以最小化程序集中的源文件和/或类的数量,那么是的,扩展方法绝对是要走的路,而抽象类最终可能会被更少使用。

然而,对于那些更关心抽象类在概念上代表什么的人来说,并没有太大变化。在代码组织上下文中将“public int getCalories()”放在抽象类“Food”中仍然是有意义的,因为吃它的人(使用食物子类的代码)定义有多少是愚蠢的它所含的卡路里。

于 2009-03-16T19:57:07.090 回答
0

一句话:是的。由于抽象类对未来实现的影响很大,一般建议似乎是创建实现接口的抽象类(例如 DBDataReader:IDataReader)。只要方法不需要访问对象的内部状态,我认为没有理由不将其呈现为扩展方法。您无需成本即可获得功能。

于 2009-03-16T19:58:17.523 回答
0

我认为在某些情况下这是一个好主意。Linq 是一个很好的例子,但还有另一个好处:流。

流是抽象类的全部原因是因为它们必须支持 BeginRead、ReadByte 等方法。流可以是一个接口,而所有这些方法都可以是扩展方法。这将使执行诸如从非流类继承和添加流功能之类的事情成为可能,并使添加潜在有用的流方法(如“将流内容转储到内存缓冲区”)(更)自然。

于 2009-03-16T20:03:42.800 回答
0

如果您不需要从扩展方法访问受保护的字段,那么这是 AFAIK 的方法,因为您可以继承多个接口并以这种方式获取所有相关的扩展(而您只能从单个抽象类派生)。

于 2009-03-16T20:10:42.800 回答
-1

我没有使用过 .NET 2.0 之后的任何东西,所以我不知道你能做到这一点。对我来说似乎很奇怪;接口不适用于实现。我会觉得这很混乱。此外,您展示的示例是一个类,而不是一个接口。

于 2009-03-16T19:49:36.220 回答