让一个类将自己作为参数传递给泛型基类邪恶?

时间:2013-10-08 11:49:22

标签: c# oop generics design-patterns

我第一次看到一位同事在实施对象池时这样做了。他将要作为参数汇集的类传递给泛型基类。这个基类列出了汇集代码。

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

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

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

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>
{
}

2 个答案:

答案 0 :(得分:14)

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

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

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

答案 1 :(得分: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#泛型在运行时是受支持的。

此外,编写这样的代码会降低可读性。因此,尽管确实存在这种方法,但您应该考虑一下您正在尝试解决的问题,看看是否有更简单的方法。