C# - 实例 - 抽象类的刚性

时间:2009-10-03 06:03:41

标签: c#

我理解

 “可以在不破坏API的情况下修改抽象类”。

一旦向该方提供了一个版本(比如1.0.0.0)的类库,当我设计另一个版本(比如1.1.0.0)进行修改时,它会不会破坏代码?

你能给出一个非常简单的例子,它怎么可能?

6 个答案:

答案 0 :(得分:3)

抽象类和接口(在较小程度上)都是我们认为的合同。抽象类可以比接口更复杂,因为它们可以具有实现以及合同定义。这两种类型都可以通过几种方式修改,而无需打破合同(API)。合同变更有三种基本类型:

  1. 添加会员
  2. 删除会员
  3. 修改成员
  4. 在C#中,成员可以是方法,属性,索引器和字段。最简单的,首先是非破坏性的变化,是成员的增加。添加成员会增加API,但绝不会更改先前存在的API。删除成员是一个重大变化,因为以前的API确实在删除成员时发生了变化。

    最终选项,即成员的修改,可能会或可能不一定会在C#中破坏。在字段的情况下,唯一的修改是重命名。重命名公共领域总是一个重大改变。可以重命名属性,也可以添加或删除setter / getter。添加setter / getter并没有破坏,但所有其他属性更改都在中断。通过在现有参数列表的末尾添加params参数,可以在不破坏合同的情况下更改索引器和方法。对索引器和方法的任何其他更改也会破坏更改。

    除API级别外,还应考虑行为变化。虽然我们应该始终努力保持API和行为尽可能分离,但并不总是像那样切割和干燥。在创建新版本时,要考虑重要的行为细微差别及其对API使用的影响。这些细微差别可能是方法抛出的异常,API成员对其他API成员的使用等等。

    一旦了解了三种更改以及它们如何影响合同,您就应该能够更好地控制对抽象类和接口进行版本控制的方式。不间断更改通常标记为次要版本更改,或者可能仅标记更改。突破性更改通常标记为主要版本更改。如果你仔细考虑版本控制,它应该是一个非常容易管理的问题......只需确保在进行重大更改之前完全理解其影响。

答案 1 :(得分:1)

在这些术语中,我将API理解为客户端代码在使用类的版本(1.0.0.0)时能够使用的合同(公共方法定义集)。只有在新版本的抽象类(1.1.0.0)中,您定义的新方法是非抽象的,才有可能“破坏API”。在版本1.1.0.0实现中 抽象的任何新方法都将“破坏API”。 (另外,更改非抽象的方法定义将“破坏API”。)。

答案 2 :(得分:1)

我认为该语句意味着可以更改抽象类中的方法体 - 而无需更改接口。

考虑到这一点:

public abstract class Animal
{
   public virtual string Speak()
   {
      return "erm";
   }
}

稍后如果您发现动物没有说erm,而是说ya,那么在您的1.1.0.0版本中,您只需将代码更改为:

public abstract class Animal
{
   public virtual string Speak()
   {
      return "ya";
   }
}

在这种情况下,如果您的客户端使用程序集版本1.0.0.0在其他类中继承Animal,那么他不必更改其代码以便使用1.1.0.0进行编译。

答案 3 :(得分:1)

首先,我会说这不是抽象类特有的,而是一般的类。

考虑以下课程:

public class SomeClass
{
    public bool IsValid(string input)
    {
        return !string.IsNullOrEmpty(input);
    }
}

它定义了一个采用string并返回bool的方法。如果字符串为null或为空,它将返回false。现在,让我们改变它:

public class SomeClass
{
    public bool IsValid(string input)
    {
        return !string.IsNullOrEmpty(input);
    }

}

在这种情况下,我们添加了一种新方法。普遍的方法是不受影响的。此更改不会以任何方式影响使用该类的代码。下一步改变:

public class SomeClass
{
    public bool IsValid(string input)
    {
        if (string.IsNullOrEmpty(input))
        {
            return false;
        }

        return input.Length > 5;
    }

    public void SomeNewMethod() {  }
}

现在我们更改了IsValid行为。旧代码仍然可以在不进行更改的情况下进行编译,但某些输入值的结果已更改。这是一种突破性变革。下一步改变:

public class SomeClass
{
    public void IsValid(DateTime input)
    {
        // do something with the input
    }

    public void SomeNewMethod() {  }
}

现在我们更改了IsValid签名。这将导致调用代码无法编译。这是另一种突破性变化。

正如您所看到的,这些破坏API的示例与该类是否为抽象无关。

答案 4 :(得分:1)

有点深奥,但是我们已经受到了这样的打击 - 如果您的程序集名称很强且包含配置数据,您可以通过更改版本号来中断代码。除非在升级程序集时升级app | web.config,否则如果使用完整绑定路径(比如引用类型),新程序集将无法加载。

更常规的答案可能是您在抽象类中修复了一个错误而无需更改任何成员。

还建议version policy,但需要在整个团队中采用。

答案 5 :(得分:1)

  

可以修改抽象类   不破坏API。

这是完全错误的,或者充其量只是误导。 API不仅是类接口的语法方面,还有它的语义 - 即某种方法的描述行为

这是我的意思的一个例子:

// v1
public abstract class A
{
    void DoSomething()
    {
        ...
        if (someCondition)
        {
            throw new SomeException();
        }
    }
}

现在在下一个版本中你可能有:

// v2
public abstract class A
{
    void DoSomething()
    {
        ...
        if (someCondition)
        {
            throw new DifferentException();
        }
    }
}

你的'API' - 似乎保持不变 - 可能看起来像这样:

public class B: A
{
    ...
    void DoSomething(); // inherited from base
}

但实际上,当用v2替换基类v1时,你没有保持API不变,因为可能有一些依赖于SomeException的调用代码被抛出,而不是DifferentException。 当然,您可以进行修改,使语法和语义保持不变,但这是您在制作新版本时始终所做的,并且有很多不同的技术。它不是特定于基类,不管是抽象的还是抽象的。