【问题标题】:Abstract class design抽象类设计
【发布时间】:2026-02-15 17:35:01
【问题描述】:

这是一个可以接受的设计吗??

抽象类

public abstract class SomethingBase
{ 
   public abstract int Method1(int number1);
   public abstract int Method2(int number2); 
} 

public class Class1 : SomethingBase
{
   public override int Method1(int number1)
   {
      //implementation
   }

   //i dont want this method in this class
   public override int Method2(int number2)
   {
        throw new NotImplementedException();
   }
}

public class Class2 : SomethingBase
{

   //i dont want this method in this class
   public override int Method1(int number1)
   {
     throw new NotImplementedException();
   }

   public override int Method2(int number2)
   {
    //implementation
    }
}

我的意思是我的 Class1 中需要 method1 而不需要 method2 而 Class2 的情况。事实上,这些方法在派生类中相互排斥。

【问题讨论】:

  • NotSupportedExceptionInvalidOperationException 在这种情况下更有意义。

标签: c# oop abstract-class


【解决方案1】:

就目前而言,这不是一个可接受的设计。为了使其更容易接受,您可以在抽象类上实现两个接口。一个用于Method1(),一个用于Method2(),只需使用接口对象传递即可。

您真的不想使用Class1Class2 定义,因为您的客户可能会调用未实现的方法。所以我建议使用接口并改用这些。

【讨论】:

    【解决方案2】:

    我认为您的设计存在根本问题。两个派生对象应该只派生自真正代表两者之间共性的基类。在您的示例中,基类不代表 Class1 和 Class2 之间的共性。

    【讨论】:

      【解决方案3】:

      休息Liskov's Substitution Principle(又名LSP):

      令 q(x) 是关于 T 类型的对象 x 的可证明性质。那么 q(y) 对于 S 类型的对象 y 应该为真,其中 S 是 T 的子类型。

      我也认为这是 IS-A 规则。如果类 B 是从类 A 派生的,它必须支持与类 A 相同的所有功能,并且不能破坏其在过程中的任何行为。如果某物消耗了 A 类对象,您必须能够将其传递给 B 类对象,而不是让它中断。

      例如,假设您的基类 is-a Rectangle,并从中派生出一个 Square 类。您已经破坏了 LSP,因为矩形的宽度可以不等于它们的长度,而 方形 则不能。

      然而,在你的情况下,你的基类是抽象的,所以 LSP 并不完全适合,但我想你明白我的意思。

      【讨论】:

        【解决方案4】:

        这是一个可以接受的设计吗??

        没有。在某些情况下我可以接受(例如 FCL 中的 ReadOnlyCollection),但您应该尽可能避免使用它。

        您可以使用两个接口:一个使用 Method1,一个使用 Method2

        您应该始终遵循派生类不应强化基类契约的规则。它们应该可以与程序中任何地方的基类进行交换。

        【讨论】:

          【解决方案5】:

          这不是一个可接受的设计,因为它违反了基类契约。以下面的客户端代码为例

          public void SomeFunction(SomethingBase x){
               x. Method2(1); // This will fail for Class1 but will pass for Class2. This is faulty.
          }
          

          相反,您可以编写一个接口,让一个接口包含 Method1,而第二个接口包含 Method2。

          interface IM1{
               int Method1(int number1);
          }
          
          interface IM2{
               int Method2(int number2);
          }
          
          class Class1 : IM1{
               int Method1(int number1){
                    // Implement your logic
               }
          }
          
          class Class2 : IM2{
               int Method2(int number2){
                    // Implement your logic
               }
          }
          
          // Client Code
              public void SomeFunction(IM2 x){
                   x. Method2(1); // This will work as this is the only thing this client wants.
              }
          

          【讨论】:

            【解决方案6】:

            我绝对同意您所展示的设计应尽可能避免使用接口的说法。

            但有时很难避免或预测。

            .NET 中使用您提到的方法的经典示例——Stream 类,其中一些子级为一些抽象属性和方法抛出 NotSupportedException。 (如DeflateStream 类中的Position 属性。) 但是为了正确实现该设计,您应该拥有虚拟的CanMethod1 属性或Method1Supported() 方法。 (如TypeConverter ——通常当您有更复杂的情况时,主要是当该方法仅在某些上下文中受支持时。)

            【讨论】:

              【解决方案7】:

              首先,如果您没有在抽象类中定义任何成员变量,我可能只会使用接口。

              其次,我可能在抽象类中只有一个方法,并且每个类都会以不同的方式实现它(使用它们自己的特定逻辑),因为 Method1 和 Method2 的签名似乎是相同的。

              【讨论】:

                【解决方案8】:

                不同意多数意见,但支持摄政王。想要支持和扩展他的职位。

                使用 NotSupportedException 并不总是一个糟糕的设计。你应该权衡所有的利弊。

                如果您有各种各样的类,它们的方法集几乎但不完全相同,该怎么办?流就是一个很好的例子。如果您尝试将所有这些方法分类到接口中,它 1) 为您提供了一项额外的工作,即为所有这些接口发明直观清晰和简洁的名称,2) 迫使接口的用户了解每个接口的作用。

                对于一个类库的用户来说,也许在一两个方法中抛出 NotSupportedException 会更清楚,然后再学习一个复杂的接口和类层次结构。

                【讨论】: