静态方法继承的正确替代方法是什么?


83

我了解C#不支持静态方法继承。我还阅读了许多讨论(包括此处的讨论),在这些讨论中,开发人员声称需要此功能,典型的回答是“如果需要静态成员继承,则设计中会有缺陷”。

好吧,鉴于OOP甚至不想让我考虑静态继承,因此我必须得出结论,我对它的明显需求指向了我的设计中的错误。但是,我被困住了。我非常感谢您为解决此问题提供的帮助。这是挑战...

我想创建一个封装了一些复杂的初始化代码的抽象基类(我们称它为Fruit)。该代码不能放在构造函数中,因为其中一些将依赖于虚拟方法调用。

Fruit将被其他具体类(Apple,Orange)继承,每个具体类都必须公开一个标准工厂方法CreateInstance()来创建和初始化实例。

如果静态成员继承是可行的,我将把工厂方法放在基类中,并使用对派生类的虚拟方法调用来获取必须从中初始化具体实例的类型。客户端代码将简单地调用Apple.CreateInstance()以获得完全初始化的Apple实例。

但是显然这是不可能的,所以有人可以解释一下我的设计需要如何更改以适应相同的功能。


2
我想指出的是,“此代码不能放置在构造函数中,因为其中一些依赖于虚方法调用”这样的语句是不正确的。您可以在构造函数中调用虚拟方法。(您必须仔细设计调用,因为在调用虚拟方法时可以有部分初始化的类,但它受支持。)有关更多详细信息,请参见Ravadre的答案。
鲁宾

Answers:


61

一个想法:

public abstract class Fruit<T>
    where T : Fruit<T>, new()
{
    public static T CreateInstance()
    {
        T newFruit = new T();
        newFruit.Initialize();  // Calls Apple.Initialize
        return newFruit;
    }

    protected abstract void Initialize();
}

public class Apple : Fruit<Apple>
{
    protected override void Initialize() { ... }
}

像这样调用:

Apple myAppleVar = Fruit<Apple>.CreateInstance();

无需额外的工厂课程。


9
恕我直言,最好将泛型类型移至CreateInstance方法,而不是将其置于类级别。(就像我在回答中所做的那样)。
Frederik Gheysels 09年

1
会记录您的偏好。简要说明您的偏爱将不胜感激。
马特·哈姆史密斯,09年

9
这样,您就不会“污染”班级的其余部分。type参数仅在Create方法中是必需的(至少在此示例中)。接下来,我认为:Fruit.Create <Apple>(); 然后更具可读性:Fruit <Apple> .Create();
Frederik Gheysels 09年

2
您不能使Apple构造函数小于公共构造函数,因为where T : Fruit<T>, new()指示T必须具有公共构造函数。@Matt Hamsmith-您将代码删除,直到将其删除protected Apple() { }。我已经在VS中对其进行了测试。
Tohid

1
@Tohid-你是正确的。我已编辑。但是,这确实使苹果无需使用Fruit CreateInstance静态工厂方法即可构建,但这似乎是不可避免的。
Matt Hamsmith 2014年

17

将factory方法移出类型,然后将其放在自己的Factory类中。

public abstract class Fruit
{
    protected Fruit() {}

    public abstract string Define();

}

public class Apple : Fruit
{
    public Apple() {}

    public override string Define()
    {
         return "Apple";
    }
}

public class Orange : Fruit
{
    public Orange() {}

    public override string Define()
    {
         return "Orange";
    }
}

public static class FruitFactory<T> 
{
     public static T CreateFruit<T>() where T : Fruit, new()
     {
         return new T();
     }
}

但是,就我而言,无需将Create方法移至其自己的Factory类(尽管我认为这是更可取的-分离关注点),您可以将其放在Fruit类中:

public abstract class Fruit
{

   public abstract string Define();

   public static T CreateFruit<T>() where T : Fruit, new()
   {
        return new T();
   }

}

并且,查看它是否有效:

    class Program
    {
        static void Main( string[] args )
        {
            Console.WriteLine (Fruit.CreateFruit<Apple> ().Define ());
            Console.WriteLine (Fruit.CreateFruit<Orange> ().Define ());

            Console.ReadLine ();
        }        
    }

1
您的代码将无法编译。您需要where T:new()子句。但是,这完全是我的欺骗。
John Gietzen 2009年

1
尽管可以创建一个静态的FruitFactory类,但我更喜欢IFruitFactory接口以及可实例化的FruitFactory类。您可能只得到一个FruitFactory类,该类的CreateFruit方法未引用其对象实例,因此也可以是静态方法,但是如果有必要提供多种创建水果的方法,或者如果创建水果的话如果需要维护状态的能力,使用实例方法可能会很有用。
supercat


4

我会做这样的事情

 public abstract class Fruit() {
      public abstract void Initialize();
 }

 public class Apple() : Fruit {
     public override void Initialize() {

     }
 }

 public class FruitFactory<T> where T : Fruit, new {
      public static <T> CreateInstance<T>() {
          T fruit = new T();
          fruit.Initialize();
          return fruit;  
      }
 } 


var fruit = FruitFactory<Apple>.CreateInstance()

3

WebRequest.NET BCL中的类及其派生类型代表了如何很好地实现这种设计的一个很好的例子。

WebRequest班有几个子类,包括HttpWebRequestFtpWebReuest。现在,该WebRequest基类也是工厂类型,并公开了一个静态Create方法(根据工厂模式的要求,实例构造函数是隐藏的)。

public static WebRequest Create(string requestUriString)
public static WebRequest Create(Uri requestUri)

Create方法返回WebRequest该类的特定实现,并使用URI(或URI字符串)确定要创建和返回的对象的类型。

这具有以下使用模式的最终结果:

var httpRequest = (HttpWebRequest)WebRequest.Create("http://stackoverflow.com/");
// or equivalently
var httpRequest = (HttpWebRequest)HttpWebWebRequest.Create("http://stackoverflow.com/");

var ftpRequest = (FtpWebRequest)WebRequest.Create("ftp://stackoverflow.com/");
// or equivalently
var ftpRequest = (FtpWebRequest)FtpWebWebRequest.Create("ftp://stackoverflow.com/");

我个人认为这是解决此问题的好方法,并且确实确实是.NET Framework创建者的首选方法。


3

首先,没有可以虚拟化的静态初始化器并不意味着您不能拥有“标准”成员方法,因为它们可能会被重载。其次,您可以从构造函数中调用虚拟方法,它们将按预期工作,因此这里没有问题。第三,您可以使用泛型来拥有类型安全的工厂。
这是一些代码,它使用由构造函数调用的factory +成员Initialize()方法(并且它是受保护的,因此您不必担心,有人在创建对象后会再次调用它):


abstract class Fruit
{
    public Fruit()
    {
        Initialize();
    }

    protected virtual void Initialize()
    {
        Console.WriteLine("Fruit.Initialize");
    }
}

class Apple : Fruit
{
    public Apple()
        : base()
    { }

    protected override void Initialize()
    {
        base.Initialize();
        Console.WriteLine("Apple.Initialize");
    }

    public override string ToString()
    {
        return "Apple";
    }
}

class Orange : Fruit
{
    public Orange()
        : base()
    { }

    protected override void Initialize()
    {
        base.Initialize();
        Console.WriteLine("Orange.Initialize");
    }

    public override string ToString()
    {
        return "Orange";
    }
}

class FruitFactory
{
    public static T CreateFruit<T>() where T : Fruit, new()
    {
        return new T();
    }
}

public class Program
{

    static void Main()
    {
        Apple apple = FruitFactory.CreateFruit<Apple>();
        Console.WriteLine(apple.ToString());

        Orange orange = new Orange();
        Console.WriteLine(orange.ToString());

        Fruit appleFruit = FruitFactory.CreateFruit<Apple>();
        Console.WriteLine(appleFruit.ToString());
    }
}

1
通常,最好避免从构造函数中调用虚拟方法。参见blogs.msdn.com/abhinaba/archive/2006/02/28/540357.aspx
TrueWill

没错,这可能很棘手,尽管它有可能并且将一直按预期工作,但问题是要了解“应该”的含义。通常,我倾向于避免调用任何更复杂的方法(即使是非虚拟的方法,也就是因为它们通常以某种方式构造,从而允许它们引发异常,并且我不喜欢我的ctor抛出任何东西) ,但这是开发人员的决定。
MarcinDeptuła09年

0

我想说的最好的办法是在必须调用的水果类上创建一个虚拟/抽象的Initialize方法,然后创建一个外部“水果工厂”类来创建实例:


public class Fruit
{
    //other members...
    public abstract void Initialise();
}

public class FruitFactory()
{
    public Fruit CreateInstance()
    {
        Fruit f = //decide which fruit to create
        f.Initialise();

        return f;
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.