16

我第一次看到一位同事在实现对象池时这样做。他将要合并的类作为参数传递给通用基类。这个基类列出了池化代码。

奇怪的是基类会知道它的孩子。在每个正常情况下,这都被认为是不好的做法。但是在这种情况下,父级只是避免编写重复代码的技术解决方案。任何其他代码都不会引用基类。

这种结构的一个缺点是它“烧毁了基类”。您不能在层次结构的中间引入通用基类。这个问题可能不在主题范围内。

下面是一个可以想象的例子:

public abstract class Singleton<T> where T : class
{
    public static T Instance { get; private set; }

    public Singleton()
    {
        if (Instance != null)
            throw new Exception("Singleton instance already created.");
        Instance = (T) (object) this;
    }
}

public class MyClass : Singleton<MyClass>
{
}

改进的代码:

public abstract class Singleton<T> where T : Singleton<T>
{
    public static T Instance { get; private set; }

    public Singleton()
    {
        if (Instance != null)
            throw new Exception("Singleton instance already created.");
        Instance = (T) this;
    }
}

public class MyClass : Singleton<MyClass>
{
}
4

2 回答 2

14

不; 这是一个众所周知的模式,称为CRTP
它在 C++ 中作为虚拟方法的替代品特别有用。

您可以在 .Net 框架中的IComparable<T>和.Net 框架中看到它IEquatable<T>

为了增加稳健性,您应该添加where T : Singleton<T>

于 2013-10-08T11:51:26.317 回答
3

SLaks 是正确的——这是一个有用的模式,通常用于当你想在你的基类中提供对你的派生类强类型化的代码时。

您通常还会向泛型参数添加类型约束,以指示泛型类型必须从抽象类型继承。添加此约束的语法看起来是递归的,但不要对此感到恐慌——它不会递归评估,只是确保唯一有效的泛型类型是派生类。

例如,假设您经营茶和咖啡混合业务。将咖啡与咖啡、茶与茶混合对您来说是有意义的,但您要确保不能将咖啡与茶混合。但是,由于它们都是饮料,因此您希望以相同的方式对它们进行建模。

public abstract class Beverage<T> where T : Beverage<T>
{
    public abstract T Blend(T drink1, T drink2);
}

public class Tea : Beverage<Tea>
{
    public override Tea Blend(Tea drink1, Tea drink2)
    { 
        // Blend tea here.
    }
}
public class Coffee : Beverage<Coffee>
{
    public override Coffee Blend(Coffee drink1, Coffee drink2)
    { 
        // Blend coffee here.  Although coffee is nasty, so
        // why you'd want to is beyond me.
    }
}

在阅读 CRTP 时,值得记住的是,C++ 模板只是表面上类似于 C# 泛型。主要区别在于模板实际上是在编译时工作的代码生成工具,而 C# 泛型在运行时受支持。

此外,编写这样的代码会降低可读性。因此,尽管在某些情况下这肯定是正确的方法,但您应该考虑一下您要解决的问题,看看是否有更直接的方法。

于 2013-10-08T12:31:38.600 回答