【问题标题】:What's the correct alternative to static method inheritance?静态方法继承的正确替代方法是什么?
【发布时间】:2023-04-09 05:47:02
【问题描述】:

我了解 C# 不支持静态方法继承。我还阅读了一些讨论(包括此处),其中开发人员声称需要此功能,典型的回应是“如果您需要静态成员继承,那么您的设计就有缺陷”。

好的,鉴于 OOP 甚至不希望我考虑静态继承,我必须得出结论,我对它的明显需求表明我的设计存在错误。但是,我被困住了。我真的很感谢一些帮助解决这个问题。这是挑战...

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

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

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

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

【问题讨论】:

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

标签: c# .net inheritance static


【解决方案1】:

一个想法:

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();

不需要额外的工厂类。

【讨论】:

  • 恕我直言,最好将泛型类型移至 CreateInstance 方法,而不是将其放在类级别。 (就像我在回答中所做的那样)。
  • 您的偏好已被记录。对您的偏好进行简要说明将不胜感激。
  • 这样做不会“污染”班级的其他成员; type 参数仅在 Create 方法中是必需的(至少在此示例中)。除此之外,我认为: Fruit.Create();那么更具可读性: Fruit.Create();
  • 您不能使Apple 构造函数小于公共,因为where T : Fruit&lt;T&gt;, new() 规定T 必须有一个公共构造函数。 @Matt Hamsmith - 在删除 protected Apple() { } 之前,您的代码不会编译。我已经在VS中测试过了。
  • @Tohid - 你是对的。我已编辑。然而,这确实暴露了 Apple 在不使用 Fruit CreateInstance 静态工厂方法的情况下构建,但这似乎是不可避免的。
【解决方案2】:

将工厂方法移出类型,并将其放入自己的工厂类中。

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 ();
        }        
    }

【讨论】:

  • 您的代码将无法编译。你需要 where T : new() 子句。然而,这完全是我的欺骗。
  • 虽然可以创建一个静态 FruitFactory 类,但我更喜欢 IFruitFactory 接口以及可实例化的 FruitFactory 类。您最终可能只有一个 FruitFactory 类,其 CreateFruit 方法不引用其对象实例,因此也可以是一个静态方法,但如果有必要提供多种创建水果的方法,或者如果创建水果永远需要维护状态的能力,使用实例方法可能很有用。
【解决方案3】:

为什么不用 create 方法创建工厂类(模板化)?

FruitFactory<Banana>.Create();

【讨论】:

    【解决方案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()
    

    【讨论】:

      【解决方案5】:

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

      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 创建者的首选方法。

      【讨论】:

        【解决方案6】:

        首先,没有可以是虚拟的静态初始化器并不意味着您不能有“标准”成员方法,它可能会被重载。其次,你可以从构造函数中调用你的虚方法,它们会按预期工作,所以这里没有问题。第三,您可以使用泛型来拥有类型安全的工厂。
        下面是一些代码,它使用由构造函数调用的工厂 + 成员 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());
            }
        }
        

        【讨论】:

        • 通常最好避免从构造函数调用虚方法。见blogs.msdn.com/abhinaba/archive/2006/02/28/540357.aspx
        • 是的,这可能很棘手,尽管它是可能的并且将始终按应有的方式工作,但问题是,要了解“应该”的含义。一般来说,我倾向于避免调用任何更复杂的方法(即使是非虚拟的,即因为它们通常以某种方式构造,允许它们抛出异常,而且我不喜欢我的 ctors 抛出任何东西) ,但这是开发人员的决定。
        【解决方案7】:

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-12-25
          • 1970-01-01
          • 2016-11-21
          • 2012-06-21
          • 2015-03-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多