接口与基类

发布于 2024-07-05 04:53:39 字数 195 浏览 6 评论 0原文

什么时候应该使用接口,什么时候应该使用基类?

如果我不想实际定义方法的基本实现,它是否应该始终是一个接口?

如果我有狗和猫课。 为什么我要实现 IPet 而不是 PetBase? 我可以理解拥有 ISheds 或 IBarks(IMakesNoise?)的接口,因为它们可以逐个宠物地放置在宠物上,但我不明白将哪个用于通用宠物。

When should I use an interface and when should I use a base class?

Should it always be an interface if I don't want to actually define a base implementation of the methods?

If I have a Dog and Cat class. Why would I want to implement IPet instead of PetBase? I can understand having interfaces for ISheds or IBarks (IMakesNoise?), because those can be placed on a pet by pet basis, but I don't understand which to use for a generic Pet.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(30

很酷不放纵 2024-07-12 04:53:39

接口上的基类的情况在 Submain .NET 编码指南中得到了很好的解释:

基类与接口
接口类型是部分的
一个值的描述,可能
许多对象类型都支持。 使用
基类而不是接口
只要有可能。 从版本控制
从角度来看,课程更加灵活
比接口。 通过课程,您可以
发布版本 1.0,然后发布版本
2.0 向类添加新方法。 只要方法不是抽象的,
任何现有的派生类继续
保持功能不变。

因为接口不支持
实现继承,
适用于类的模式确实
不适用于接口。 添加一个
接口的方法是等价的
向基础添加抽象方法
班级; 任何实现的类
接口会因为类而中断
不实现新方法。
接口适用于
以下情况:

  1. 几个不相关的类想要支持该协议。
  2. 这些类已经建立了基类(例如
    例子,
    有些是用户界面 (UI) 控件,
    有些是 XML Web 服务)。
  3. 汇总是不适当或不可行的。 在所有其他
    情况,
    类继承是一个更好的模型。

The case for Base Classes over Interfaces was explained well in the Submain .NET Coding Guidelines:

Base Classes vs. Interfaces
An interface type is a partial
description of a value, potentially
supported by many object types. Use
base classes instead of interfaces
whenever possible. From a versioning
perspective, classes are more flexible
than interfaces. With a class, you can
ship Version 1.0 and then in Version
2.0 add a new method to the class. As long as the method is not abstract,
any existing derived classes continue
to function unchanged.

Because interfaces do not support
implementation inheritance, the
pattern that applies to classes does
not apply to interfaces. Adding a
method to an interface is equivalent
to adding an abstract method to a base
class; any class that implements the
interface will break because the class
does not implement the new method.
Interfaces are appropriate in the
following situations:

  1. Several unrelated classes want to support the protocol.
  2. These classes already have established base classes (for
    example,
    some are user interface (UI) controls,
    and some are XML Web services).
  3. Aggregation is not appropriate or practicable. In all other
    situations,
    class inheritance is a better model.
韶华倾负 2024-07-12 04:53:39

一个重要的区别是您只能继承一个基类,但可以实现许多接口。 因此,只有当您绝对确定不需要继承不同的基类时,您才需要使用基类。 此外,如果您发现您的接口变得越来越大,那么您应该开始将其分解为几个定义独立功能的逻辑部分,因为没有规则表明您的类不能全部实现它们(或者您可以定义不同的功能)只是继承它们的接口来对它们进行分组)。

One important difference is that you can only inherit one base class, but you can implement many interfaces. So you only want to use a base class if you are absolutely certain that you won't need to also inherit a different base class. Additionally, if you find your interface is getting large then you should start looking to break it up into a few logical pieces that define independent functionality, since there's no rule that your class can't implement them all (or that you can define a different interface that just inherits them all to group them).

恬淡成诗 2024-07-12 04:53:39

当我第一次开始学习面向对象编程时,我犯了一个简单且可能常见的错误,即使用继承来共享公共行为 - 即使该行为对于对象的本质来说并不重要。

为了进一步构建这个特定问题中经常使用的示例,有很多东西是可宠物的 - 女朋友,汽车,毛茸茸的毯子...... - 所以我可能有一个可宠物类提供这个常见行为以及继承它的各种类。

然而,可抚摸并不是这些物体的本质的一部分。 还有一些更重要的概念对于它们的本性至关重要 - 女朋友是一个人,汽车是陆地车辆,猫是哺乳动物......

行为应该首先分配给界面(包括类的默认接口),并且仅当它们对于作为较大类的子集的一大类类是通用的时才提升为基类 - 与“猫”和“人”是子集的含义相同的“哺乳动物”。

问题是,当你比我一开始更好地理解面向对象设计之后,你通常会自动执行此操作,甚至不需要考虑它。 因此,“为接口编写代码,而不是抽象类”这句话的赤裸裸的事实变得如此明显,你很难相信有人会费心去说它 - 并开始尝试解读它的其他含义。

我要补充的另一件事是,如果一个类纯粹是抽象的 - 没有非抽象、非继承的成员或暴露给子级、父级或客户端的方法 - 那么为什么它是一个类? 在某些情况下,它可以被接口替换,而在其他情况下,可以被 Null 替换。

When I first started learning about object-oriented programming, I made the easy and probably common mistake of using inheritance to share common behavior - even where that behavior was not essential to the nature of the object.

To further build on an example much used in this particular question, there are lots of things that are petable - girlfriends, cars, fuzzy blankets... - so I might have had a Petable class that provided this common behavior, and various classes inheriting from it.

However, being petable is not part of the nature of any of these objects. There are vastly more important concepts that are essential to their nature - the girlfriend is a person, the car is a land vehicle, the cat is a mammal...

Behaviors should be assigned first to interfaces (including the default interface of the class), and promoted to a base class only if they are (a) common to a large group of classes that are subsets of a larger class - in the same sense that "cat" and "person" are subsets of "mammal".

The catch is, after you understand object-oriented design sufficiently better than I did at first, you'll normally do this automatically without even thinking about it. So the bare truth of the statement "code to an interface, not an abstract class" becomes so obvious you have a hard time believing anyone would bother to say it - and start trying to read other meanings into it.

Another thing I'd add is that if a class is purely abstract - with no non-abstract, non-inherited members or methods exposed to child, parent, or client - then why is it a class? It could be replaced, in some cases by an interface and in other cases by Null.

明月夜 2024-07-12 04:53:39

优先选择接口而不是抽象类

基本原理,
要考虑的要点[这里已经提到了两点]是:

  • 接口更加灵活,因为一个类可以实现多个
    接口。 由于Java没有多重继承,因此使用
    抽象类阻止您的用户使用任何其他类
    等级制度。 一般来说,在没有默认值的情况下更喜欢接口
    实现或状态。
    Java 集合提供了很好的示例
    这个(地图、集合等)。
  • 抽象类的优点是可以更好地转发
    兼容性。 一旦客户端使用了某个接口,您就无法更改它;
    如果他们使用抽象类,您仍然可以添加行为而无需
    破坏现有代码。 如果担心兼容性,请考虑使用
    抽象类。
  • 即使您确实有默认实现或内部状态,
    考虑提供一个接口及其抽象实现
    这将有助于客户,但仍然允许他们更大的自由,如果
    所需的[1]。
    当然,这个话题已经被详细讨论过
    其他地方[2,3]。

[1] 当然,它添加了更多代码,但如果简洁性是您最关心的问题,那么您可能应该首先避免使用 Java!

[2] Joshua Bloch,《有效的 Java》,第 16-18 条。

[3] http://www.codeproject.com/KB/ar...

Prefer interfaces over abstract classes

Rationale,
the main points to consider [two already mentioned here] are :

  • Interfaces are more flexible, because a class can implement multiple
    interfaces. Since Java does not have multiple inheritance, using
    abstract classes prevents your users from using any other class
    hierarchy. In general, prefer interfaces when there are no default
    implementations or state.
    Java collections offer good examples of
    this (Map, Set, etc.).
  • Abstract classes have the advantage of allowing better forward
    compatibility. Once clients use an interface, you cannot change it;
    if they use an abstract class, you can still add behavior without
    breaking existing code. If compatibility is a concern, consider using
    abstract classes.
  • Even if you do have default implementations or internal state,
    consider offering an interface and an abstract implementation of it.
    This will assist clients, but still allow them greater freedom if
    desired [1].
    Of course, the subject has been discussed at length
    elsewhere [2,3].

[1] It adds more code, of course, but if brevity is your primary concern, you probably should have avoided Java in the first place!

[2] Joshua Bloch, Effective Java, items 16-18.

[3] http://www.codeproject.com/KB/ar...

眼眸印温柔 2024-07-12 04:53:39

之前关于使用抽象类进行通用实现的评论绝对是中肯的。 我还没有看到提到的一个好处是,使用接口可以更轻松地实现模拟对象以进行单元测试。 正如 Jason Cohen 所描述的那样定义 IPet 和 PetBase 使您能够轻松模拟不同的数据条件,而无需物理数据库的开销(直到您决定测试真实的东西)。

Previous comments about using abstract classes for common implementation is definitely on the mark. One benefit I haven't seen mentioned yet is that the use of interfaces makes it much easier to implement mock objects for the purpose of unit testing. Defining IPet and PetBase as Jason Cohen described enables you to mock different data conditions easily, without the overhead of a physical database (until you decide it's time to test the real thing).

捎一片雪花 2024-07-12 04:53:39

不要使用基类,除非您知道它的含义并且它适用于这种情况。 如果适用,则使用它,否则,使用接口。 但请注意有关小接口的答案。

公共继承在 OOD 中被过度使用,并且表达的内容比大多数开发人员意识到或愿意实现的要多得多。 请参阅里氏可替代性原理

简而言之,如果A“是a”B,则A不再需要对于它公开的每个方法,其交付量均不低于 B,并且交付的结果不低于 B。

Don't use a base class unless you know what it means, and that it applies in this case. If it applies, use it, otherwise, use interfaces. But note the answer about small interfaces.

Public Inheritance is overused in OOD and expresses a lot more than most developers realize or are willing to live up to. See the Liskov Substitutablity Principle

In short, if A "is a" B then A requires no more than B and delivers no less than B, for every method it exposes.

墨小沫ゞ 2024-07-12 04:53:39

从概念上讲,接口用于正式和半正式定义对象将提供的一组方法。 正式意味着一组方法名称和签名,半正式意味着与这些方法相关的人类可读文档。

接口只是 API 的描述(毕竟,API 代表应用程序编程接口),它们不能包含任何实现,并且不可能使用或运行接口。 它们只是明确了你应该如何与对象交互的契约。

类提供一种实现,并且它们可以声明它们实现零个、一个或多个接口。 如果要被继承,则约定是在类名前加上“Base”前缀。

基类抽象基类 (ABC) 之间存在区别。 ABC 将接口和实现混合在一起。 计算机编程之外的抽象意味着“概括”,即“抽象==接口”。 然后,抽象基类可以描述接口以及要继承的空的、部分的或完整的实现。

关于何时使用接口抽象基类以及何时使用的观点将根据您正在开发的内容和语言而有很大差异接口通常仅与静态类型语言(例如 Java 或 C#)相关联,但动态类型语言也可以具有接口抽象基类。 例如,在 Python 中,类(声明它实现接口)和对象(类的实例)之间的区别很明显,据说提供接口。 在动态语言中,两个同属于同一个类的实例的对象可以声明它们提供完全不同的接口。 在 Python 中,这仅适用于对象属性,而方法是类的所有对象之间共享状态。 然而,在 Ruby 中,对象可以拥有每个实例的方法,因此同一的两个对象之间的接口可能会根据程序员的需要而有所不同(但是,Ruby 没有任何显式的方法来声明接口)。

在动态语言中,通常会隐式假定对象的接口,要么通过内省对象并询问它提供哪些方法(三思而后行),或者最好通过简单地尝试使用所需的对象上的接口,并在对象不提供该接口时捕获异常(请求宽恕比请求许可更容易)。 这可能会导致“误报”,其中两个接口具有相同的方法名称,但语义不同。 但是,权衡是您的代码更加灵活,因为您不需要预先过度指定来预测代码的所有可能用途。

Conceptually, an interface is used to formally and semi-formally define a set of methods that an object will provide. Formally means a set of method names and signatures, and semi-formally means human readable documentation associated with those methods.

Interfaces are only descriptions of an API (after all, API stands for application programming interface), they can't contain any implementation, and it's not possible to use or run an interface. They only make explicit the contract of how you should interact with an object.

Classes provide an implementation, and they can declare that they implement zero, one or more Interfaces. If a class is intended to be inherited, the convention is to prefix the class name with "Base".

There is a distinction between a base class and an abstract base classes (ABC). ABCs mix interface and implementation together. Abstract outside of computer programming means "summary", that is "abstract == interface". An abstract base class can then describe both an interface, as well as an empty, partial or complete implementation that is intended to be inherited.

Opinions on when to use interfaces versus abstract base classes versus just classes is going to vary wildly based on both what you are developing, and which language you are developing in. Interfaces are often associated only with statically typed languages such as Java or C#, but dynamically typed languages can also have interfaces and abstract base classes. In Python for example, the distinction is made clear between a Class, which declares that it implements an interface, and an object, which is an instance of a class, and is said to provide that interface. It's possible in a dynamic language that two objects that are both instances of the same class, can declare that they provide completely different interfaces. In Python this is only possible for object attributes, while methods are shared state between all objects of a class. However, in Ruby, objects can have per-instance methods, so it's possible that the interface between two objects of the same class can vary as much as the programmer desires (however, Ruby doesn't have any explicit way of declaring Interfaces).

In dynamic languages the interface to an object is often implicitly assumed, either by introspecting an object and asking it what methods it provides (look before you leap) or preferably by simply attempting to use the desired interface on an object and catching exceptions if the object doesn't provide that interface (easier to ask forgiveness than permission). This can lead to "false positives" where two interfaces have the same method name, but are semantically different. However, the trade-off is that your code is more flexible since you don't need to over specify up-front to anticipate all possible uses of your code.

楠木可依 2024-07-12 04:53:39

要记住的另一个选择是使用“has-a”关系,也称为“根据”或“组合”实现。 有时,与使用“is-a”继承相比,这是一种更干净、更灵活的结构方式。

从逻辑上来说,说 Dog 和 Cat 都“拥有”一个 Pet 可能没有多大意义,但它避免了常见的多重继承陷阱:

public class Pet
{
    void Bathe();
    void Train(Trick t);
}

public class Dog
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

public class Cat
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

是的,这个例子表明,做事时存在大量代码重复且缺乏优雅性这边走。 但人们还应该意识到,这有助于使 Dog 和 Cat 与 Pet 类解耦(因为 Dog 和 Cat 无法访问 Pet 的私有成员),并且它为 Dog 和 Cat 留出了继承其他对象的空间 - -可能是哺乳动物类。

当不需要私有访问并且不需要使用通用 Pet 引用/指针来引用 Dog 和 Cat 时,组合是更好的选择。 接口为您提供通用参考功能,并有助于减少代码的冗长性,但当代码组织不良时,它们也会使代码变得混乱。 当您需要私有成员访问时,继承非常有用,并且在使用它时,您将自己承诺将 Dog 和 Cat 类与 Pet 类高度耦合,这是一个高昂的成本。

在继承、组合和接口之间,没有一种方法是永远正确的,这有助于考虑如何协调使用这三种选项。 在这三者中,继承通常是最不常用的选项。

Another option to keep in mind is using the "has-a" relationship, aka "is implemented in terms of" or "composition." Sometimes this is a cleaner, more flexible way to structure things than using "is-a" inheritance.

It may not make as much sense logically to say that Dog and Cat both "have" a Pet, but it avoids common multiple inheritance pitfalls:

public class Pet
{
    void Bathe();
    void Train(Trick t);
}

public class Dog
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

public class Cat
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

Yes, this example shows that there is a lot of code duplication and lack of elegance involved in doing things this way. But one should also appreciate that this helps to keep Dog and Cat decoupled from the Pet class (in that Dog and Cat do not have access to the private members of Pet), and it leaves room for Dog and Cat to inherit from something else--possibly the Mammal class.

Composition is preferable when no private access is required and you don't need to refer to Dog and Cat using generic Pet references/pointers. Interfaces give you that generic reference capability and can help cut down on the verbosity of your code, but they can also obfuscate things when they are poorly organized. Inheritance is useful when you need private member access, and in using it you are committing yourself to highly coupling your Dog and Cat classes to your Pet class, which is a steep cost to pay.

Between inheritance, composition, and interfaces there is no one way that is always right, and it helps to consider how all three options can be used in harmony. Of the three, inheritance is typically the option that should be used the least often.

演出会有结束 2024-07-12 04:53:39

基类的继承者应该具有“是”关系。 接口代表“实现”关系。
因此,仅当您的继承者将维持关系时才使用基类。

An inheritor of a base class should have an "is a" relationship. Interface represents An "implements a" relationship.
So only use a base class when your inheritors will maintain the is a relationship.

孤千羽 2024-07-12 04:53:39

使用接口在不相关的类家族之间强制执行契约。 例如,您可能对表示集合的类有通用的访问方法,但包含完全不同的数据,即一个类可能表示查询的结果集,而另一个类可能表示图库中的图像。 此外,您还可以实现多个接口,从而允许您混合(并表示)类的功能。

当类具有共同关系并因此具有相似的结构和行为特征时,请使用继承,即汽车、摩托车、卡车和 SUV 都是可能包含多个车轮、最高速度的道路车辆类型

Use Interfaces to enforce a contract ACROSS families of unrelated classes. For example, you might have common access methods for classes that represent collections, but contain radically different data i.e. one class might represent a result set from a query, while the other might represent the images in a gallery. Also, you can implement multiple interfaces, thus allowing you to blend (and signify) the capabilities of the class.

Use Inheritance when the classes bear a common relationship and therefore have a similair structural and behavioural signature, i.e. Car, Motorbike, Truck and SUV are all types of road vehicle that might contain a number of wheels, a top speed

顾忌 2024-07-12 04:53:39

我发现 Interface > 的模式 摘要> 具体在以下用例中工作:

1.  You have a general interface (eg IPet)
2.  You have a implementation that is less general (eg Mammal)
3.  You have many concrete members (eg Cat, Dog, Ape)

抽象类定义具体类的默认共享属性,但强制执行接口。 例如:

public interface IPet{

    public boolean hasHair();

    public boolean walksUprights();

    public boolean hasNipples();
}

现在,由于所有哺乳动物都有头发和乳头(据我所知,我不是动物学家),我们可以将其放入抽象基类中

public abstract class Mammal() implements IPet{

     @override
     public walksUpright(){
         throw new NotSupportedException("Walks Upright not implemented");
     }

     @override
     public hasNipples(){return true}

     @override
     public hasHair(){return true}

,然后具体类仅定义它们直立行走。

public class Ape extends Mammal(){

    @override
    public walksUpright(return true)
}

public class Catextends Mammal(){

    @override
    public walksUpright(return false)
}

当有很多具体类,并且您不想维护样板文件只是为了对接口进行编程时,这种设计很好。 如果将新方法添加到接口中,则会破坏所有生成的类,因此您仍然可以获得接口方法的优点。

在这种情况下,抽象也可以是具体的; 然而,抽象名称有助于强调正在采用这种模式。

I've found that a pattern of Interface > Abstract > Concrete works in the following use-case:

1.  You have a general interface (eg IPet)
2.  You have a implementation that is less general (eg Mammal)
3.  You have many concrete members (eg Cat, Dog, Ape)

The abstract class defines default shared attributes of the concrete classes, yet enforces the interface. For example:

public interface IPet{

    public boolean hasHair();

    public boolean walksUprights();

    public boolean hasNipples();
}

Now, since all mammals have hair and nipples (AFAIK, I'm not a zoologist), we can roll this into the abstract base class

public abstract class Mammal() implements IPet{

     @override
     public walksUpright(){
         throw new NotSupportedException("Walks Upright not implemented");
     }

     @override
     public hasNipples(){return true}

     @override
     public hasHair(){return true}

And then the concrete classes merely define that they walk upright.

public class Ape extends Mammal(){

    @override
    public walksUpright(return true)
}

public class Catextends Mammal(){

    @override
    public walksUpright(return false)
}

This design is nice when there are lots of concrete classes, and you don't want to maintain boilerplate just to program to an interface. If new methods were added to the interface, it would break all of the resulting classes, so you are still getting the advantages of the interface approach.

In this case, the abstract could just as well be concrete; however, the abstract designation helps to emphasize that this pattern is being employed.

菩提树下叶撕阳。 2024-07-12 04:53:39

这取决于您的要求。 如果 IPet 足够简单,我更愿意实现它。 否则,如果 PetBase 实现了大量您不想重复的功能,那就使用它吧。

实现基类的缺点是需要覆盖(或)现有方法。 这使它们成为虚拟方法,这意味着您必须小心如何使用对象实例。

最后,.NET 的单一继承杀死了我。 一个简单的例子:假设您正在创建一个用户控件,因此您继承了UserControl。 但是,现在您也无法继承 PetBase。 这迫使您重新组织,例如创建一个 PetBase 类成员。

It depends on your requirements. If IPet is simple enough, I would prefer to implement that. Otherwise, if PetBase implements a ton of functionality you don't want to duplicate, then have at it.

The downside to implementing a base class is the requirement to override (or new) existing methods. This makes them virtual methods which means you have to be careful about how you use the object instance.

Lastly, the single inheritance of .NET kills me. A naive example: Say you're making a user control, so you inherit UserControl. But, now you're locked out of also inheriting PetBase. This forces you to reorganize, such as to make a PetBase class member, instead.

唔猫 2024-07-12 04:53:39

我通常不会实现其中任何一个,除非我需要一个。 与抽象类相比,我更喜欢接口,因为接口提供了更多的灵活性。 如果某些继承类中有共同的行为,我会将其向上移动并创建一个抽象基类。 我不认为两者都有必要,因为它们本质上服务于相同的目的,并且两者都具有不好的代码味道(恕我直言),解决方案已经被过度设计。

I usually don't implement either until I need one. I favor interfaces over abstract classes because that gives a little more flexibility. If there's common behavior in some of the inheriting classes I move that up and make an abstract base class. I don't see the need for both, since they essentially server the same purpose, and having both is a bad code smell (imho) that the solution has been over-engineered.

芸娘子的小脾气 2024-07-12 04:53:39

对于 C#,在某种意义上接口和抽象类是可以互换的。 然而,不同之处在于: i) 接口不能实现代码; ii) 因此,接口无法进一步调用堆栈的子类; iii) 一个类只能继承抽象类,而一个类可以实现多个接口。

Regarding C#, in some senses interfaces and abstract classes can be interchangeable. However, the differences are: i) interfaces cannot implement code; ii) because of this, interfaces cannot call further up the stack to subclass; and iii) only can abstract class may be inherited on a class, whereas multiple interfaces may be implemented on a class.

末が日狂欢 2024-07-12 04:53:39

按照定义,接口提供了一个与其他代码进行通信的层。 类的所有公共属性和方法默认都实现隐式接口。 我们还可以将接口定义为角色,当任何类需要扮演该角色时,它必须实现它,根据实现它的类给予不同的实现形式。 因此,当您谈论接口时,您正在谈论多态性,而当您谈论基类时,您正在谈论继承。 哎呀两个概念!!!

By def, interface provides a layer to communicate with other code. All the public properties and methods of a class are by default implementing implicit interface. We can also define an interface as a role, when ever any class needs to play that role, it has to implement it giving it different forms of implementation depending on the class implementing it. Hence when you talk about interface, you are talking about polymorphism and when you are talking about base class, you are talking about inheritance. Two concepts of oops !!!

明明#如月 2024-07-12 04:53:39

这是 .NET 特有的,但《框架设计指南》一书认为,一般来说,类在不断发展的框架中提供了更大的灵活性。 一旦接口发布,您就没有机会在不破坏使用该接口的代码的情况下更改它。 然而,对于类,您可以修改它,而不会破坏链接到它的代码。 只要您进行正确的修改(包括添加新功能),您就能够扩展和发展您的代码。

Krzysztof Cwalina 在第 81 页说道:

在 .NET Framework 的三个版本的过程中,我与我们团队中的不少开发人员讨论了该指南。 他们中的许多人,包括那些最初不同意这些准则的人,都表示他们后悔将一些 API 作为接口提供。 我什至没有听说过有人后悔他们送了一门课的案例。

话虽如此,接口肯定有一席之地。 作为一般准则,如果没有其他目的,则始终提供接口的抽象基类实现作为实现接口的方法的示例。 在最好的情况下,基类将节省大量工作。

This is pretty .NET specific, but the Framework Design Guidelines book argues that in general classes give more flexibility in an evolving framework. Once an interface is shipped, you don't get the chance to change it without breaking code that used that interface. With a class however, you can modify it and not break code that links to it. As long you make the right modifications, which includes adding new functionality, you will be able to extend and evolve your code.

Krzysztof Cwalina says on page 81:

Over the course of the three versions of the .NET Framework, I have talked about this guideline with quite a few developers on our team. Many of them, including those who initially disagreed with the guidelines, have said that they regret having shipped some API as an interface. I have not heard of even one case in which somebody regretted that they shipped a class.

That being said there certainly is a place for interfaces. As a general guideline always provide an abstract base class implementation of an interface if for nothing else as an example of a way to implement the interface. In the best case that base class will save a lot of work.

望笑 2024-07-12 04:53:39

Juan,

我喜欢将接口视为描述类的一种方式。 特定的狗品种类(例如约克夏梗犬)可能是父狗类的后代,但它也实现了 IFurry、IStubby 和 IYippieDog。 因此,类定义了类是什么,但接口告诉我们有关它的信息。

这样做的好处是,例如,它允许我收集所有 IYippieDog 并将它们放入我的海洋收藏中。 因此,现在我可以遍历一组特定的对象并找到符合我正在查看的标准的对象,而无需过于仔细地检查类。

我发现接口确实应该定义类的公共行为的子集。 如果它为所有实现的类定义了所有公共行为,那么它通常不需要存在。 他们没有告诉我任何有用的东西。

但这种想法与每个类都应该有一个接口并且您应该针对该接口进行编码的想法背道而驰。 这很好,但最终会得到很多一对一的类接口,这会让事情变得混乱。 我的理解是,这样做并不需要任何成本,而且现在您可以轻松地换入和换出物品。 然而,我发现我很少这样做。 大多数时候,我只是修改现有的类,如果该类的公共接口需要更改,我会遇到完全相同的问题,除了我现在必须在两个地方更改它。

因此,如果您像我一样思考,您肯定会说 Cat 和 Dog 是 IPettable。 这是一个与他们两者相匹配的特征。

另一个问题是它们应该具有相同的基类吗? 问题是它们是否需要被广泛地视为同一事物。 当然,它们都是动物,但这符合我们一起使用它们的方式吗?

假设我想收集所有动物类别并将它们放入我的方舟容器中。

或者它们必须是哺乳动物吗? 也许我们需要某种跨动物挤奶工厂?

它们是否需要连接在一起? 只知道它们都是 IPettable 就足够了吗?

当我实际上只需要一个类时,我经常感到渴望派生整个类层次结构。 我这样做是因为预期有一天我可能会需要它,但通常我从来不会这样做。 即使我这样做了,我通常也会发现我必须做很多事情来解决它。 那是因为我创建的第一个类不是狗,我没那么幸运,而是鸭嘴兽。 现在我的整个类层次结构都基于奇怪的情况,并且我有很多浪费的代码。

在某些时候,您可能还会发现并非所有的猫都是 IPettable(就像那只无毛的猫)。 现在您可以将该接口移动到所有适合的派生类。 您会发现一个不那么重大的变化是 Cats 突然不再派生自 PettableBase。

Juan,

I like to think of interfaces as a way to characterize a class. A particular dog breed class, say a YorkshireTerrier, may be a descended of the parent dog class, but it is also implements IFurry, IStubby, and IYippieDog. So the class defines what the class is but the interface tells us things about it.

The advantage of this is it allows me to, for example, gather all the IYippieDog's and throw them into my Ocean collection. So now I can reach across a particular set of objects and find ones that meet the criteria I am looking at without inspecting the class too closely.

I find that interfaces really should define a sub-set of the public behavior of a class. If it defines all the public behavior for all the classes that implement then it usually does not need to exist. They do not tell me anything useful.

This thought though goes counter to the idea that every class should have an interface and you should code to the interface. That's fine, but you end up with a lot of one to one interfaces to classes and it makes things confusing. I understand that the idea is it does not really cost anything to do and now you can swap things in and out with ease. However, I find that I rarely do that. Most of the time I am just modifying the existing class in place and have the exact same issues I always did if the public interface of that class needs changing, except I now have to change it in two places.

So if you think like me you would definitely say that Cat and Dog are IPettable. It is a characterization that matches them both.

The other piece of this though is should they have the same base class? The question is do they need to be broadly treated as the same thing. Certainly they are both Animals, but does that fit how we are going to use them together.

Say I want to gather all Animal classes and put them in my Ark container.

Or do they need to be Mammals? Perhaps we need some kind of cross animal milking factory?

Do they even need to be linked together at all? Is it enough to just know they are both IPettable?

I often feel the desire to derive a whole class hierarchy when I really just need one class. I do it in anticipation someday I might need it and usually I never do. Even when I do, I usually find I have to do a lot to fix it. That’s because the first class I am creating is not the Dog, I am not that lucky, it is instead the Platypus. Now my entire class hierarchy is based on the bizarre case and I have a lot of wasted code.

You might also find at some point that not all Cats are IPettable (like that hairless one). Now you can move that Interface to all the derivative classes that fit. You will find that a much less breaking change that all of a sudden Cats are no longer derived from PettableBase.

云朵有点甜 2024-07-12 04:53:39

这篇Java World 文章<中对此进行了很好的解释/a>.

就我个人而言,我倾向于使用接口来定义接口,即系统设计中指定应如何访问某些内容的部分。

我有一个类实现一个或多个接口,这并不罕见。

我使用抽象类作为其他东西的基础。

以下是上述文章的摘录 JavaWorld.com 文章,作者 Tony Sintes,2001 年 4 月 20 日


接口与抽象类

选择接口和抽象类不是一个非此即彼的命题。 如果您需要更改设计,请将其设为界面。 但是,您可能有提供某些默认行为的抽象类。 抽象类是应用程序框架内的优秀候选者。

抽象类可以让你定义一些行为; 它们迫使你的子类提供其他子类。 例如,如果您有一个应用程序框架,则抽象类可能会提供默认服务,例如事件和消息处理。 这些服务允许您的应用程序插入到您的应用程序框架中。 但是,有一些特定于应用程序的功能只有您的应用程序才能执行。 此类功能可能包括启动和关闭任务,这些任务通常依赖于应用程序。 因此,抽象基类可以声明抽象关闭和启动方法,而不是尝试定义该行为本身。 基类知道它需要这些方法,但是抽象类让您的类承认它不知道如何执行这些操作; 它只知道它必须发起行动。 当需要启动时,抽象类可以调用启动方法。 当基类调用此方法时,Java 会调用子类定义的方法。

许多开发人员忘记了定义抽象方法的类也可以调用该方法。 抽象类是创建计划继承层次结构的绝佳方法。 对于类层次结构中的非叶类来说,它们也是一个不错的选择。

类与接口

有人说你应该根据接口来定义所有类,但我认为建议似乎有点极端。 当我发现我的设计中的某些内容会频繁更改时,我会使用界面。

例如,策略模式允许您将新算法和流程交换到程序中,而无需更改使用它们的对象。 媒体播放器可能知道如何播放 CD、MP3 和 wav 文件。 当然,您不想将这些播放算法硬编码到播放器中; 这将使添加 AVI 等新格式变得困难。 此外,您的代码将充斥着无用的 case 语句。 雪上加霜的是,每次添加新算法时,您都需要更新这些案例语句。 总而言之,这不是一种非常面向对象的编程方式。

使用策略模式,您可以简单地将算法封装在对象后面。 如果您这样做,您就可以随时提供新的媒体插件。 我们将插件类称为 MediaStrategy。 该对象有一个方法:playStream(Stream s)。 因此,要添加新算法,我们只需扩展算法类即可。 现在,当程序遇到新的媒体类型时,它只是将流的播放委托给我们的媒体策略。 当然,您需要一些管道来正确实例化您需要的算法策略。

这是使用界面的绝佳场所。 我们使用了策略模式,它清楚地表明了设计中将会改变的地方。 因此,您应该将策略定义为接口。 当您希望对象具有某种类型时,您通常应该优先考虑接口而不是继承; 在本例中为 MediaStrategy。 依赖继承来实现类型标识是危险的; 它将您锁定在特定的继承层次结构中。 Java 不允许多重继承,因此您无法扩展某些东西来为您提供有用的实现或更多类型标识。

It is explained well in this Java World article.

Personally, I tend to use interfaces to define interfaces - i.e. parts of the system design that specify how something should be accessed.

It's not uncommon that I will have a class implementing one or more interfaces.

Abstract classes I use as a basis for something else.

The following is an extract from the above mentioned article JavaWorld.com article, author Tony Sintes, 04/20/01


Interface vs. abstract class

Choosing interfaces and abstract classes is not an either/or proposition. If you need to change your design, make it an interface. However, you may have abstract classes that provide some default behavior. Abstract classes are excellent candidates inside of application frameworks.

Abstract classes let you define some behaviors; they force your subclasses to provide others. For example, if you have an application framework, an abstract class may provide default services such as event and message handling. Those services allow your application to plug in to your application framework. However, there is some application-specific functionality that only your application can perform. Such functionality might include startup and shutdown tasks, which are often application-dependent. So instead of trying to define that behavior itself, the abstract base class can declare abstract shutdown and startup methods. The base class knows that it needs those methods, but an abstract class lets your class admit that it doesn't know how to perform those actions; it only knows that it must initiate the actions. When it is time to start up, the abstract class can call the startup method. When the base class calls this method, Java calls the method defined by the child class.

Many developers forget that a class that defines an abstract method can call that method as well. Abstract classes are an excellent way to create planned inheritance hierarchies. They're also a good choice for nonleaf classes in class hierarchies.

Class vs. interface

Some say you should define all classes in terms of interfaces, but I think recommendation seems a bit extreme. I use interfaces when I see that something in my design will change frequently.

For example, the Strategy pattern lets you swap new algorithms and processes into your program without altering the objects that use them. A media player might know how to play CDs, MP3s, and wav files. Of course, you don't want to hardcode those playback algorithms into the player; that will make it difficult to add a new format like AVI. Furthermore, your code will be littered with useless case statements. And to add insult to injury, you will need to update those case statements each time you add a new algorithm. All in all, this is not a very object-oriented way to program.

With the Strategy pattern, you can simply encapsulate the algorithm behind an object. If you do that, you can provide new media plug-ins at any time. Let's call the plug-in class MediaStrategy. That object would have one method: playStream(Stream s). So to add a new algorithm, we simply extend our algorithm class. Now, when the program encounters the new media type, it simply delegates the playing of the stream to our media strategy. Of course, you'll need some plumbing to properly instantiate the algorithm strategies you will need.

This is an excellent place to use an interface. We've used the Strategy pattern, which clearly indicates a place in the design that will change. Thus, you should define the strategy as an interface. You should generally favor interfaces over inheritance when you want an object to have a certain type; in this case, MediaStrategy. Relying on inheritance for type identity is dangerous; it locks you into a particular inheritance hierarchy. Java doesn't allow multiple inheritance, so you can't extend something that gives you a useful implementation or more type identity.

陌若浮生 2024-07-12 04:53:39

这是接口和基类的基本且简单的定义:

  • 基类=对象继承。
  • 接口=功能继承。

干杯

Here is the basic and simple definiton of interface and base class:

  • Base class = object inheritance.
  • Interface = functional inheritance.

cheers

那小子欠揍 2024-07-12 04:53:39

我建议尽可能使用组合而不是继承。 使用接口但使用成员对象进行基本实现。 这样,您就可以定义一个工厂来构造对象以某种方式运行。 如果您想更改行为,那么您可以创建一个新的工厂方法(或抽象工厂)来创建不同类型的子对象。

在某些情况下,如果所有可变行为都在辅助对象中定义,您可能会发现您的主要对象根本不需要接口。

因此,您最终可能会得到一个具有 IFurBehavior 参数的 Pet,而不是 IPet 或 PetBase。 IFurBehavior 参数由 PetFactory 的 CreateDog() 方法设置。 shed() 方法调用的就是这个参数。

如果这样做,您会发现您的代码更加灵活,并且大多数简单对象都处理非常基本的系统范围行为。

即使在多重继承语言中,我也推荐这种模式。

I recommend using composition instead of inheritence whenever possible. Use interfaces but use member objects for base implementation. That way, you can define a factory that constructs your objects to behave in a certain way. If you want to change the behavior then you make a new factory method (or abstract factory) that creates different types of sub-objects.

In some cases, you may find that your primary objects don't need interfaces at all, if all of the mutable behavior is defined in helper objects.

So instead of IPet or PetBase, you might end up with a Pet which has an IFurBehavior parameter. The IFurBehavior parameter is set by the CreateDog() method of the PetFactory. It is this parameter which is called for the shed() method.

If you do this you'll find your code is much more flexible and most of your simple objects deal with very basic system-wide behaviors.

I recommend this pattern even in multiple-inheritence languages.

久而酒知 2024-07-12 04:53:39

还要记住不要被 OO 冲昏了头脑 (参见博客)并始终根据所需的行为对对象进行建模,如果您正在设计一个应用程序,其中您需要的唯一行为是动物的通用名称和物种,那么您只需要一个具有属性的 Animal 类名称,而不是世界上每种可能的动物的数百万个类别。

Also keep in mind not to get swept away in OO (see blog) and always model objects based on behavior required, if you were designing an app where the only behavior you required was a generic name and species for an animal then you would only need one class Animal with a property for the name, instead of millions of classes for every possible animal in the world.

GRAY°灰色天空 2024-07-12 04:53:39

来源http://jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/

C# 是一门美妙的语言,在过去 14 年里已经成熟和发展。 这对我们开发人员来说非常有用,因为成熟的语言为我们提供了大量可供我们使用的语言功能。

然而,权力越大,责任就越大。 其中一些功能可能会被滥用,或者有时很难理解为什么您会选择使用一种功能而不是另一种功能。 多年来,我看到许多开发人员遇到的一个问题是何时选择使用接口或选择使用抽象类。 两者各有优缺点以及正确的使用时间和地点。 但我们该如何决定呢?

两者都提供了类型之间公共功能的重用。 最明显的区别是接口不提供其功能的实现,而抽象类允许您实现一些“基本”或“默认”行为,然后能够在必要时使用类派生类型“覆盖”此默认行为。

这一切都很好,并且提供了代码的良好重用,并遵守软件开发的 DRY(不要重复自己)原则。 当您具有“is a”关系时,抽象类非常有用。

例如:金毛猎犬“是一种”狗。 贵宾犬也是如此。 它们都会吠叫,就像所有的狗一样。 但是,您可能想要说明贵宾犬公园与“默认”狗叫声有很大不同。 因此,您可以实现如下所示的内容:

public abstract class Dog
{
      public virtual void Bark()
      {
        Console.WriteLine("Base Class implementation of Bark");
      }
}

public class GoldenRetriever : Dog
{
   // the Bark method is inherited from the Dog class
}

public class Poodle : Dog
{
  // here we are overriding the base functionality of Bark with our new implementation
  // specific to the Poodle class
  public override void Bark()
  {
     Console.WriteLine("Poodle's implementation of Bark");
  }
}

// Add a list of dogs to a collection and call the bark method.

void Main()
{
    var poodle = new Poodle();
    var goldenRetriever = new GoldenRetriever();

    var dogs = new List<Dog>();
    dogs.Add(poodle);
    dogs.Add(goldenRetriever);

    foreach (var dog in dogs)
    {
       dog.Bark();
    }
}

// Output will be:
// Poodle's implementation of Bark
// Base Class implementation of Bark

// 

如您所见,这将是保持代码干燥的好方法,并允许在任何类型只能依赖于默认值时调用基类实现吠叫而不是特殊情况的实现。 像 GoldenRetriever、Boxer、Lab 这样的类都可以免费继承“默认”(低音类)Bark,因为它们实现了 Dog 抽象类。

但我相信你已经知道了。

您来到这里是因为您想了解为什么您可能希望选择接口而不是抽象类,反之亦然。 您可能想要选择接口而不是抽象类的原因之一是当您没有或想要阻止默认实现时。 这通常是因为实现接口的类型不以“是”关系相关。 实际上,除了每种类型“能够”或“有能力”做某事或拥有某事之外,它们根本不必相关。

这到底是什么意思? 嗯,举个例子:人不是鸭子……鸭子也不是人。 很明显。 然而,鸭子和人类都有游泳的“能力”(假设人类在一年级就通过了游泳课:))。 另外,由于鸭子不是人,反之亦然,这不是一种“是”关系,而是一种“能够”关系,我们可以使用一个接口来说明这一点:

// Create ISwimable interface
public interface ISwimable
{
      public void Swim();
}

// Have Human implement ISwimable Interface
public class Human : ISwimable

     public void Swim()
     {
        //Human's implementation of Swim
        Console.WriteLine("I'm a human swimming!");
     }

// Have Duck implement ISwimable interface
public class Duck: ISwimable
{
     public void Swim()
     {
          // Duck's implementation of Swim
          Console.WriteLine("Quack! Quack! I'm a Duck swimming!")
     }
}

//Now they can both be used in places where you just need an object that has the ability "to swim"

public void ShowHowYouSwim(ISwimable somethingThatCanSwim)
{
     somethingThatCanSwim.Swim();
}

public void Main()
{
      var human = new Human();
      var duck = new Duck();

      var listOfThingsThatCanSwim = new List<ISwimable>();

      listOfThingsThatCanSwim.Add(duck);
      listOfThingsThatCanSwim.Add(human);

      foreach (var something in listOfThingsThatCanSwim)
      {
           ShowHowYouSwim(something);
      }
}

 // So at runtime the correct implementation of something.Swim() will be called
 // Output:
 // Quack! Quack! I'm a Duck swimming!
 // I'm a human swimming!

使用像上面的代码这样的接口将允许您将对象传递给“能够”执行某些操作的方法。 代码并不关心它是如何做到的……它只知道它可以调用该对象的 Swim 方法,并且该对象将根据其类型知道在运行时采取哪种行为。

再次,这可以帮助您的代码保持 DRY,这样您就不必编写多个调用对象的方法来执行相同的核心函数(ShowHowHumanSwims(人类)、ShowHowDuckSwims(鸭)等)。

在这里使用接口允许调用方法时不必担心什么类型是什么类型或者行为是如何实现的。 它只知道给定接口,每个对象都必须实现 Swim 方法,因此可以安全地在自己的代码中调用它,并允许在自己的类中处理 Swim 方法的行为。

摘要:

所以我的主要经验法则是,当您想要为类层次结构或/和您正在使用的类或类型共享“是”关系(例如贵宾犬“是一种”类型的狗)。

另一方面,当您没有“是”关系但具有共享“能力”做某事或拥有某事的类型时,请使用接口(例如,鸭子“不是”人类。但是,鸭子和人类共享游泳的“能力”)。

抽象类和接口之间需要注意的另一个区别是,一个类可以实现一对多接口,但一个类只能从一个抽象类(或任何与此相关的类)继承。 是的,您可以嵌套类并具有继承层次结构(许多程序都这样做并且应该具有继承层次结构),但您不能在一个派生类定义中继承两个类(此规则适用于 C#。在其他一些语言中,您通常可以这样做只是因为这些语言缺乏接口)。

还要记住在使用接口时要遵守接口隔离原则 (ISP)。 ISP 声明不应强迫任何客户端依赖其不使用的方法。 因此,接口应该专注于特定任务,并且通常非常小(例如 IDisposable、IComparable )。

另一个技巧是,如果您正在开发小而简洁的功能,请使用接口。 如果您正在设计大型功能单元,请使用抽象类。

希望这能为某些人解决问题!

另外,如果您能想到任何更好的示例或想要指出一些内容,请在下面的评论中这样做!

Source: http://jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/

C# is a wonderful language that has matured and evolved over the last 14 years. This is great for us developers because a mature language provides us with a plethora of language features that are at our disposal.

However, with much power becomes much responsibility. Some of these features can be misused, or sometimes it is hard to understand why you would choose to use one feature over another. Over the years, a feature that I have seen many developers struggle with is when to choose to use an interface or to choose to use an abstract class. Both have there advantages and disadvantages and the correct time and place to use each. But how to we decide???

Both provide for reuse of common functionality between types. The most obvious difference right away is that interfaces provide no implementation for their functionality whereas abstract classes allow you to implement some “base” or “default” behavior and then have the ability to “override” this default behavior with the classes derived types if necessary.

This is all well and good and provides for great reuse of code and adheres to the DRY (Don’t Repeat Yourself) principle of software development. Abstract classes are great to use when you have an “is a” relationship.

For example: A golden retriever “is a” type of dog. So is a poodle. They both can bark, as all dogs can. However, you might want to state that the poodle park is significantly different than the “default” dog bark. Therefor, it could make sense for you to implement something as follows:

public abstract class Dog
{
      public virtual void Bark()
      {
        Console.WriteLine("Base Class implementation of Bark");
      }
}

public class GoldenRetriever : Dog
{
   // the Bark method is inherited from the Dog class
}

public class Poodle : Dog
{
  // here we are overriding the base functionality of Bark with our new implementation
  // specific to the Poodle class
  public override void Bark()
  {
     Console.WriteLine("Poodle's implementation of Bark");
  }
}

// Add a list of dogs to a collection and call the bark method.

void Main()
{
    var poodle = new Poodle();
    var goldenRetriever = new GoldenRetriever();

    var dogs = new List<Dog>();
    dogs.Add(poodle);
    dogs.Add(goldenRetriever);

    foreach (var dog in dogs)
    {
       dog.Bark();
    }
}

// Output will be:
// Poodle's implementation of Bark
// Base Class implementation of Bark

// 

As you can see, this would be a great way to keep your code DRY and allow for the base class implementation be called when any of the types can just rely on the default Bark instead of a special case implementation. The classes like GoldenRetriever, Boxer, Lab could all could inherit the “default” (bass class) Bark at no charge just because they implement the Dog abstract class.

But I’m sure you already knew that.

You are here because you want to understand why you might want to choose an interface over an abstract class or vice versa. Well one reason you may want to choose an interface over an abstract class is when you don’t have or want to prevent a default implementation. This is usually because the types that are implementing the interface not related in an “is a” relationship. Actually, they don’t have to be related at all except for the fact that each type “is able” or has “the ablity” to do something or have something.

Now what the heck does that mean? Well, for example: A human is not a duck…and a duck is not a human. Pretty obvious. However, both a duck and a human have “the ability” to swim (given that the human passed his swimming lessons in 1st grade :) ). Also, since a duck is not a human or vice versa, this is not an “is a” realationship, but instead an “is able” relationship and we can use an interface to illustrate that:

// Create ISwimable interface
public interface ISwimable
{
      public void Swim();
}

// Have Human implement ISwimable Interface
public class Human : ISwimable

     public void Swim()
     {
        //Human's implementation of Swim
        Console.WriteLine("I'm a human swimming!");
     }

// Have Duck implement ISwimable interface
public class Duck: ISwimable
{
     public void Swim()
     {
          // Duck's implementation of Swim
          Console.WriteLine("Quack! Quack! I'm a Duck swimming!")
     }
}

//Now they can both be used in places where you just need an object that has the ability "to swim"

public void ShowHowYouSwim(ISwimable somethingThatCanSwim)
{
     somethingThatCanSwim.Swim();
}

public void Main()
{
      var human = new Human();
      var duck = new Duck();

      var listOfThingsThatCanSwim = new List<ISwimable>();

      listOfThingsThatCanSwim.Add(duck);
      listOfThingsThatCanSwim.Add(human);

      foreach (var something in listOfThingsThatCanSwim)
      {
           ShowHowYouSwim(something);
      }
}

 // So at runtime the correct implementation of something.Swim() will be called
 // Output:
 // Quack! Quack! I'm a Duck swimming!
 // I'm a human swimming!

Using interfaces like the code above will allow you to pass an object into a method that “is able” to do something. The code doesn’t care how it does it…All it knows is that it can call the Swim method on that object and that object will know which behavior take at run-time based on its type.

Once again, this helps your code stay DRY so that you would not have to write multiple methods that are calling the object to preform the same core function (ShowHowHumanSwims(human), ShowHowDuckSwims(duck), etc.)

Using an interface here allows the calling methods to not have to worry about what type is which or how the behavior is implemented. It just knows that given the interface, each object will have to have implemented the Swim method so it is safe to call it in its own code and allow the behavior of the Swim method be handled within its own class.

Summary:

So my main rule of thumb is use an abstract class when you want to implement a “default” functionality for a class hierarchy or/and the classes or types you are working with share a “is a” relationship (ex. poodle “is a” type of dog).

On the other hand use an interface when you do not have an “is a” relationship but have types that share “the ability” to do something or have something (ex. Duck “is not” a human. However, duck and human share “the ability” to swim).

Another difference to note between abstract classes and interfaces is that a class can implement one to many interfaces but a class can only inherit from ONE abstract class (or any class for that matter). Yes, you can nest classes and have an inheritance hierarchy (which many programs do and should have) but you cannot inherit two classes in one derived class definition (this rule applies to C#. In some other languages you are able to do this, usually only because of the lack of interfaces in these languages).

Also remember when using interfaces to adhere to the Interface Segregation Principle (ISP). ISP states that no client should be forced to depend on methods it does not use. For this reason interfaces should be focused on specific tasks and are usually very small (ex. IDisposable, IComparable ).

Another tip is if you are developing small, concise bits of functionality, use interfaces. If you are designing large functional units, use an abstract class.

Hope this clears things up for some people!

Also if you can think of any better examples or want to point something out, please do so in the comments below!

浅黛梨妆こ 2024-07-12 04:53:39

让我们以 Dog 和 Cat 类为例,然后使用 C# 进行说明:

狗和猫都是动物,特别是四足哺乳动物(动物太笼统了)。 让我们假设您有一个抽象类 Mammal,对于它们两者:

public abstract class Mammal

这个基类可能会有默认方法,例如:

  • Feed
  • Mate

所有这些行为在两个物种之间都具有或多或少相同的实现。 要定义这一点,您将拥有:

public class Dog : Mammal
public class Cat : Mammal

现在让我们假设还有其他哺乳动物,我们通常会在动物园中看到它们:

public class Giraffe : Mammal
public class Rhinoceros : Mammal
public class Hippopotamus : Mammal

这仍然有效,因为功能的核心是 Feed()Mate() 仍然是一样的。

然而,长颈鹿、犀牛和河马并不是可以用来当宠物的动物。 这就是接口有用的地方:

public interface IPettable
{
    IList<Trick> Tricks{get; set;}
    void Bathe();
    void Train(Trick t);
}

上述合约的实现在猫和狗之间是不一样的; 将它们的实现放在抽象类中进行继承将是一个坏主意。

您的 Dog 和 Cat 定义现在应该如下所示:

public class Dog : Mammal, IPettable
public class Cat : Mammal, IPettable

理论上,您可以从更高的基类重写它们,但本质上,接口允许您仅将需要的内容添加到类中,而不需要继承。

因此,由于您通常只能从一个抽象类继承(在大多数静态类型的 OO 语言中,...例外情况包括 C++),但能够实现多个接口,因此它允许您根据需要以严格的方式构造对象基础。

Let's take your example of a Dog and a Cat class, and let's illustrate using C#:

Both a dog and a cat are animals, specifically, quadruped mammals (animals are waaay too general). Let us assume that you have an abstract class Mammal, for both of them:

public abstract class Mammal

This base class will probably have default methods such as:

  • Feed
  • Mate

All of which are behavior that have more or less the same implementation between either species. To define this you will have:

public class Dog : Mammal
public class Cat : Mammal

Now let's suppose there are other mammals, which we will usually see in a zoo:

public class Giraffe : Mammal
public class Rhinoceros : Mammal
public class Hippopotamus : Mammal

This will still be valid because at the core of the functionality Feed() and Mate() will still be the same.

However, giraffes, rhinoceros, and hippos are not exactly animals that you can make pets out of. That's where an interface will be useful:

public interface IPettable
{
    IList<Trick> Tricks{get; set;}
    void Bathe();
    void Train(Trick t);
}

The implementation for the above contract will not be the same between a cat and dog; putting their implementations in an abstract class to inherit will be a bad idea.

Your Dog and Cat definitions should now look like:

public class Dog : Mammal, IPettable
public class Cat : Mammal, IPettable

Theoretically you can override them from a higher base class, but essentially an interface allows you to add on only the things you need into a class without the need for inheritance.

Consequently, because you can usually only inherit from one abstract class (in most statically typed OO languages that is... exceptions include C++) but be able to implement multiple interfaces, it allows you to construct objects in a strictly as required basis.

逆光飞翔i 2024-07-12 04:53:39

接口和基类代表两种不同形式的关系。

继承(基类)表示“is-a”关系。 例如,狗或猫“是”宠物。 这种关系始终代表类的(单一)目的(与“单一职责原则”)。

另一方面,接口代表类的附加功能。 我将其称为“是”关系,就像“Foo 是一次性的”一样,因此是 C# 中的 IDisposable 接口。

Interfaces and base classes represent two different forms of relationships.

Inheritance (base classes) represent an "is-a" relationship. E.g. a dog or a cat "is-a" pet. This relationship always represents the (single) purpose of the class (in conjunction with the "single responsibility principle").

Interfaces, on the other hand, represent additional features of a class. I'd call it an "is" relationship, like in "Foo is disposable", hence the IDisposable interface in C#.

一桥轻雨一伞开 2024-07-12 04:53:39

好吧,乔什·布洛赫 (Josh Bloch) 在 Effective Java 2d 中说道:

更喜欢接口而不是抽象

类要点:

  • 现有类可以轻松改造以实现新的
    界面。 您所要做的就是添加
    所需的方法(如果还没有)
    存在并添加一个 Implements 子句
    类声明。

  • 接口是定义 mixin 的理想选择。 宽松地说,一个
    mixin 是一个类可以的类型
    除了其“主要
    type”来声明它提供
    一些可选的行为。 例如,
    Comparable 是一个 mixin 接口
    允许一个类声明它的
    实例的排序是关于
    其他相互可比的对象。

  • 接口允许构建非层次类型
    框架。 类型层次结构是
    非常适合组织一些事情,但是
    其他事情并不能完全归入
    严格的等级制度。

  • 接口可通过以下方式实现安全、强大的功能增强
    包装类习惯用法。 如果你使用
    抽象类来定义类型,你
    留下想要添加的程序员
    功能别无选择,但
    使用继承。

而且,你可以将美德结合起来
接口和抽象类的组成
提供抽象的骨架
每个的实现类
您导出的重要接口。

另一方面,接口很难进化。 如果您向接口添加一个方法,它将破坏它的所有实现。

PS:买本书。 它更详细。

Well, Josh Bloch said himself in Effective Java 2d:

Prefer interfaces over abstract classes

Some main points:

  • Existing classes can be easily retrofitted to implement a new
    interface
    . All you have to do is add
    the required methods if they don’t yet
    exist and add an implements clause to
    the class declaration.

  • Interfaces are ideal for defining mixins. Loosely speaking, a
    mixin is a type that a class can
    implement in addition to its “primary
    type” to declare that it provides
    some optional behavior. For example,
    Comparable is a mixin interface that
    allows a class to declare that its
    instances are ordered with respect to
    other mutually comparable objects.

  • Interfaces allow the construction of nonhierarchical type
    frameworks
    . Type hierarchies are
    great for organizing some things, but
    other things don’t fall neatly into a
    rigid hierarchy.

  • Interfaces enable safe, powerful functionality enhancements via the
    wrap- per class idiom. If you use
    abstract classes to define types, you
    leave the programmer who wants to add
    functionality with no alternative but
    to use inheritance.

Moreover, you can combine the virtues
of interfaces and abstract classes by
providing an abstract skeletal
implementation class to go with each
nontrivial interface that you export.

On the other hand, interfaces are very hard to evolve. If you add a method to an interface it'll break all of it's implementations.

PS.: Buy the book. It's a lot more detailed.

冷心人i 2024-07-12 04:53:39

现代风格是定义 IPet PetBase。

该接口的优点是其他代码可以使用它,而与其他可执行代码没有任何联系。 完全“干净”。 接口也可以混合。

但基类对于简单的实现和通用实用程序很有用。 因此,还提供一个抽象基类以节省时间和代码。

Modern style is to define IPet and PetBase.

The advantage of the interface is that other code can use it without any ties whatsoever to other executable code. Completely "clean." Also interfaces can be mixed.

But base classes are useful for simple implementations and common utilities. So provide an abstract base class as well to save time and code.

魔法少女 2024-07-12 04:53:39

我有一个粗略的经验法则

功能:所有部分可能都不同:界面。

数据和功能,部分大部分相同,部分不同:抽象类。

数据和功能,实际工作,如果仅进行轻微更改即可扩展:普通(具体)类

数据和功能,没有计划更改:带有final修饰符的普通(具体)类。

数据,也许还有功能:只读:枚举成员。

这是非常粗糙和准备好的,根本没有严格定义,但是有一个范围,从接口中的所有内容都旨在更改为枚举,其中所有内容都有点像只读文件一样固定。

I have a rough rule-of-thumb

Functionality: likely to be different in all parts: Interface.

Data, and functionality, parts will be mostly the same, parts different: abstract class.

Data, and functionality, actually working, if extended only with slight changes: ordinary (concrete) class

Data and functionality, no changes planned: ordinary (concrete) class with final modifier.

Data, and maybe functionality: read-only: enum members.

This is very rough and ready and not at all strictly defined, but there is a spectrum from interfaces where everything is intended to be changed to enums where everything is fixed a bit like a read-only file.

箜明 2024-07-12 04:53:39

接口

  • 大多数语言允许您实现多个接口
  • 修改接口是一项重大更改。 所有实现都需要重新编译/修改。
  • 所有成员都是公开的。 实施必须实施所有成员。
  • 接口有助于解耦。 您可以使用模拟框架来模拟接口背后的任何内容
  • 接口通常表示一种行为
  • 接口实现彼此解耦/隔离

基类

  • 允许您添加一些免费获得的默认实现派生(从 C# 8.0 开始,通过接口可以有默认实现)
  • 除 C++ 外,只能从一个类派生。 即使可以来自多个班级,这通常也是一个坏主意。
  • 更改基类相对容易。 派生不需要做任何特殊的事情
  • 基类可以声明可以通过派生访问的受保护和公共函数
  • 抽象基类不能像接口一样被轻松模拟 基类
  • 通常指示类型层次结构 (ISA)
  • 类派生可能会依赖于一些基本行为(对父实现有复杂的了解)。 如果你对一个人的基本实现进行了更改并破坏了其他人,事情可能会变得一团糟。

Interfaces

  • Most languages allow you to implement multiple interfaces
  • Modifying an interface is a breaking change. All implementations need to be recompiled/modified.
  • All members are public. Implementations have to implement all members.
  • Interfaces help in Decoupling. You can use mock frameworks to mock out anything behind an interface
  • Interfaces normally indicate a kind of behavior
  • Interface implementations are decoupled / isolated from each other

Base classes

  • Allows you to add some default implementation that you get for free by derivation (From C# 8.0 by interface you can have default implementation)
  • Except C++, you can only derive from one class. Even if could from multiple classes, it is usually a bad idea.
  • Changing the base class is relatively easy. Derivations do not need to do anything special
  • Base classes can declare protected and public functions that can be accessed by derivations
  • Abstract Base classes can't be mocked easily like interfaces
  • Base classes normally indicate type hierarchy (IS A)
  • Class derivations may come to depend on some base behavior (have intricate knowledge of parent implementation). Things can be messy if you make a change to the base implementation for one guy and break the others.
苍风燃霜 2024-07-12 04:53:39

接口应该很小。 真的很小。 如果您确实要分解对象,那么您的接口可能只包含一些非常具体的方法和属性。

抽象类是快捷方式。 PetBase 的所有衍生品是否都有一些东西,您只需编写一次即可完成? 如果是,那么是时候创建一个抽象类了。

抽象类也有限制。 虽然它们为您提供了一种生成子对象的绝佳快捷方式,但任何给定对象只能实现一个抽象类。 很多时候,我发现这是抽象类的限制,这就是我使用大量接口的原因。

抽象类可能包含多个接口。 您的 PetBase 抽象类可以实现 IPet(宠物有主人)和 IDigestion(宠物吃东西,或者至少他们应该吃东西)。 然而,PetBase 可能不会实现 IMammal,因为并非所有宠物都是哺乳动物,也并非所有哺乳动物都是宠物。 您可以添加一个扩展 PetBase 的 MammalPetBase 并添加 IMammal。 FishBase 可以有 PetBase 并添加 IFish。 IFish 将 ISwim 和 IUnderwaterBreather 作为接口。

是的,对于简单的示例来说,我的示例过于复杂,但这是接口和抽象类如何协同工作的伟大之处的一部分。

Interfaces should be small. Really small. If you're really breaking down your objects, then your interfaces will probably only contain a few very specific methods and properties.

Abstract classes are shortcuts. Are there things that all derivatives of PetBase share that you can code once and be done with? If yes, then it's time for an abstract class.

Abstract classes are also limiting. While they give you a great shortcut to producing child objects, any given object can only implement one abstract class. Many times, I find this a limitation of Abstract classes, and this is why I use lots of interfaces.

Abstract classes may contain several interfaces. Your PetBase abstract class may implement IPet (pets have owners) and IDigestion (pets eat, or at least they should). However, PetBase will probably not implement IMammal, since not all pets are mammals and not all mammals are pets. You may add a MammalPetBase that extends PetBase and add IMammal. FishBase could have PetBase and add IFish. IFish would have ISwim and IUnderwaterBreather as interfaces.

Yes, my example is extensively over-complicated for the simple example, but that's part of the great thing about how interfaces and abstract classes work together.

云淡风轻 2024-07-12 04:53:39

一般来说,您应该更喜欢接口而不是抽象类。 使用抽象类的原因之一是具体类之间是否有共同的实现。 当然,您仍然应该声明一个接口 (IPet) 并让一个抽象类 (PetBase) 实现该接口。使用小型、不同的接口,您可以使用多个接口来进一步提高灵活性。 接口允许最大程度的跨边界类型的灵活性和可移植性。 跨边界传递引用时,始终传递接口而不是具体类型。 这允许接收端确定具体实施并提供最大的灵活性。 当以 TDD/BDD 方式编程时,这绝对是正确的。

四人帮在他们的书中指出“因为继承将子类暴露给其父类的实现细节,所以人们常说‘继承破坏了封装’”。 我相信这是真的。

In general, you should favor interfaces over abstract classes. One reason to use an abstract class is if you have common implementation among concrete classes. Of course, you should still declare an interface (IPet) and have an abstract class (PetBase) implement that interface.Using small, distinct interfaces, you can use multiples to further improve flexibility. Interfaces allow the maximum amount of flexibility and portability of types across boundaries. When passing references across boundaries, always pass the interface and not the concrete type. This allows the receiving end to determine concrete implementation and provides maximum flexibility. This is absolutely true when programming in a TDD/BDD fashion.

The Gang of Four stated in their book "Because inheritance exposes a subclass to details of its parent's implementation, it's often said that 'inheritance breaks encapsulation". I believe this to be true.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文