简体   繁体   English

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

[英]C# - Practical example - rigidness of abstract class

I understand that 我明白那个

"Abstract classes can be modified without breaking the API ". “可以在不破坏API的情况下修改抽象类”。

once a version (say 1.0.0.0) of class library is supplied to the party,when i design the another version (say 1.1.0.0) with modification,won't it break the code ? 一旦向聚会提供了类库的一个版本(例如1.0.0.0),当我设计带有修改的另一个版本(例如1.1.0.0)时,是否会破坏代码?

can you give very simple example ,how is it possible ? 您能否举一个非常简单的例子,这怎么可能?

Abstract classes, and interfaces (to a lesser degree), are both what we consider a contract. 抽象类和接口(在较小程度上)都是我们认为的契约。 Abstract classes can be more complex than interfaces in that they can have implementation as well as a contract definition. 抽象类可能比接口更复杂,因为它们可以具有实现以及契约定义。 Both types can be modified without breaking a contract (API) in a couple ways. 可以通过两种方式修改这两种类型,而无需违反合同(API)。 There are three basic kinds of contract changes: 合同变更共有三种基本类型:

  1. Add a member 新增成员
  2. Remove a member 删除会员
  3. Modify a member 修改会员

In C#, members can be methods, properties, indexers, and fields. 在C#中,成员可以是方法,属性,索引器和字段。 The simplest, and first non-breaking change, is additions of members. 最简单的第一个不变的更改是增加成员。 Adding a member augments the API, but in no way changes the API that existed previously. 添加成员会增强API,但绝不会更改以前存在的API。 Removal of a member is a breaking change, as the previous API does change when a member is removed. 删除成员是一项重大更改,因为删除成员时以前的API确实会更改。

The final option, modification of members, may or may not necessarily be breaking in C#. 最终的选择,成员的修改,可能会或不一定会破坏C#。 In the case of fields, the only modification is a rename. 对于字段,唯一的修改是重命名。 Renaming a public field is always a breaking change. 重命名公共领域始终是一项重大突破。 Properties could be renamed, or they could have a setter/getter added or removed. 可以重命名属性,也可以添加或删除设置器/获取器。 Adding a setter/getter is not breaking, but all other property changes are breaking. 添加setter / getter不会中断,但是所有其他属性更改都将中断。 Indexers and methods may be changed without breaking contract by the addition of a params parameter at the end of an existing parameter list. 通过在现有参数列表的末尾添加params参数,可以更改索引器和方法而不会破坏合同。 Any other changes to indexers and methods would also be breaking changes. 索引器和方法的任何其他更改也将破坏更改。

Beyond the API level, behavioral changes should also be taken into account. 除了API级别,还应考虑行为更改。 While we should always strive to keep the API and behavior as decoupled as possible, it is not always as cut and dry as that. 虽然我们应该始终努力使API和行为尽可能地分离,但并不总是那么干枯。 Take important behavioral nuances and their effect on the use of an API into account when creating a new version. 创建新版本时,请考虑重要的行为细微差别及其对API使用的影响。 Such nuances might be exceptions thrown by a method, usage of other API members by an API member, etc. 这种细微差别可能是方法抛出的异常,API成员对其他API成员的使用等。

Once you understand the three kinds of changes and how they affect a contract, you should be able to better control how you version your abstract classes and interfaces. 一旦了解了三种变更及其对合同的影响,就应该能够更好地控制抽象类和接口的版本控制。 Non-breaking changes are often labeled with a minor version change, or perhaps only a revision change. 不间断的更改通常标有次要的版本更改,或者可能只是修订版本。 Breaking changes are often labeled with a major version change. 重大更改通常标有主要版本更改。 If you take a careful approach to versioning, it should be a very manageable problem...just make sure you fully understand the impact before making breaking changes. 如果您采取谨慎的版本控制方法,那么这应该是一个非常易于管理的问题……只要在进行重大更改之前确保完全了解其影响即可。

In these terms, I understand the API as the contract (set of public method definitions) that client code is able to utilize to when using version (1.0.0.0) of the class. 用这些术语,我将API理解为客户端代码在使用类版本(1.0.0.0)时可以利用的契约(公共方法定义集)。 Not "breaking the API", is possible only if, in the new version of the abstract class (1.1.0.0), the new methods you are defining are non-abstract. 只有在新版本的抽象类(1.1.0.0)中,您定义的新方法是非抽象的时,才可能无法“破坏API”。 Any new methods that are abstract in version 1.1.0.0 implements will "break the API". 1.1.0.0版抽象的任何新方法将“破坏API”。 (Also, altering method definitions that are non-abstract will "break the API".). (此外,更改非抽象的方法定义将“破坏API”。)

I think that statement means that the method body in abstract class can be changed-- without changing the interface. 我认为该语句意味着可以更改抽象类中的方法主体,而无需更改接口。

Considering this: 考虑到这一点:

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

Later if you find out that the Animal is not speaking erm , but speaking ya , so in your version 1.1.0.0, you can just change the code to: 稍后,如果您发现Animal不是说erm而是说ya ,那么在1.1.0.0版中,您可以将代码更改为:

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

In this case, if your client inherit Animal in other classes using your assembly version 1.0.0.0, then he doesn't have to change his code in order to compile with your 1.1.0.0. 在这种情况下,如果客户端使用程序集版本1.0.0.0在其他类中继承Animal,则他不必更改其代码即可与1.1.0.0进行编译。

First, i would say that this is not specific to abstract classes, but to classes in general. 首先,我要说的是,这并不是抽象类所特有的,而是一般而言。

Consider the following class: 考虑以下类别:

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

It defines a method that takes a string and returns a bool . 它定义了一个接受string并返回bool It will return false if the string is null or empty. 如果字符串为null或为空,它将返回false Now, let's change it: 现在,让我们对其进行更改:

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

}

In this case we added a new method. 在这种情况下,我们添加了一种新方法。 The previus method is untouched. 以前的方法是不变的。 This change does not in any way affect code that uses the class. 此更改不会以任何方式影响使用该类的代码。 Next change: 下一步更改:

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

        return input.Length > 5;
    }

    public void SomeNewMethod() {  }
}

Now we have altered the behaviour of IsValid . 现在,我们更改了IsValid行为 Old code will still complile without alteration, but the result for some input values have changed. 旧代码仍将不加改动地进行编译,但是某些输入值的结果已更改。 This is one form of breaking change. 这是突破性变化的一种形式。 Next change: 下一步更改:

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

    public void SomeNewMethod() {  }
}

Now we have altered the signature of IsValid . 现在,我们更改了IsValid签名 This will cause calling code to not compile. 这将导致调用代码无法编译。 This is another type of breaking change. 这是另一种重大变化。

And as you can see, these examples of breaking API has nothing to do with whether the class is abstract or not. 如您所见,这些破坏API的示例与该类是否抽象无关。

A little esoteric, but we've been hit by this - If your assembly is strong named and contains configuration data, you can break code by changing the version number. 有点深奥,但我们为此感到震惊-如果您的程序集具有强大的名称并包含配置数据,则可以通过更改版本号来破坏代码。 Unless you upgrade app|web.config when you upgrade assemblies, if a full binding path is used (say to reference a type), the new assembly will fail to load. 除非在升级程序集时升级app | web.config,否则如果使用完整的绑定路径(例如引用类型),则新程序集将无法加载。

A more conventional answer could be you fixed a bug in the abstract class without needing to change any members. 一个更常规的答案可能是您修复了抽象类中的一个错误,而无需更改任何成员。

A version policy is also recommeded, but it needs team-wide adoption to work. 还建议使用版本策略 ,但是需要在团队范围内采用。

Abstract classes can be modified without breaking the API. 可以在不破坏API的情况下修改抽象类。

That's just plain wrong, or totally misleading at best. 那是完全错误的,或充其量是完全误导的。 An API is not only the syntactical aspect of a classes interface, but also its semantics - ie the described behaviour of a certain method. API不仅是类接口的语法方面,而且还是其语义-即某种方法的描述行为

Here's an example of what I mean: 这是我的意思的示例:

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

Now in the next version you might have: 现在,在下一版本中,您可能会:

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

And your 'API' - seemingly remaining unchanged - might look like this: 而且您的“ API”(似乎保持不变)可能看起来像这样:

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

But actually, when replacing the base class v1 with v2, you didn't keep the API constant, because there might be some calling code that relies on SomeException to be thrown, not DifferentException . 但是实际上,当用v2替换基类v1时,您并没有保持API不变,因为可能有一些调用代码依赖SomeException而不是DifferentException抛出。 Sure, you can make modifications that leave both syntax and semantics unchanged, but that's what you always do when making a new version, and there's a lot of different techniques for that. 当然,您可以进行修改,使语法和语义都保持不变,但这是制作新版本时经常要做的事情,并且有很多不同的技术可以做到。 It's not specific to base classes, be they abstract or not. 它不是特定于基类的,无论它们是否抽象。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM