抽象类 vs. 接口 vs. mixins

Posted

技术标签:

【中文标题】抽象类 vs. 接口 vs. mixins【英文标题】:Abstract classes vs. interfaces vs. mixins 【发布时间】:2010-10-29 10:29:06 【问题描述】:

有人可以向我解释一下抽象类接口mixins之间的区别吗?我以前在我的代码中使用过每个,但我不知道技术差异。

【问题讨论】:

重新标记为“oop”,因为它似乎比标记“概念”更相关。 【参考方案1】:

抽象类

抽象类是不被设计为实例化的类。抽象类可以没有实现、某些实现或全部实现。抽象类旨在允许其子类共享一个通用(默认)实现。抽象类的(伪编码)示例将是这样的

abstract class Shape 
    def abstract area();  // abstract (unimplemented method)
    def outline_width() =  return 1;   // default implementation

子类可能看起来像

class Rectangle extends Shape 
    int height = width = 5;
    def override area() =  return height * width;   // implements abstract method
    // no need to override outline_width(), but may do so if needed

可能的用途

def main() = 
    Shape[] shapes =  new Rectangle(), new Oval() ;
    foreach (s in shapes) 
        print("area: " + s.area() + ", outline width: " + s.outline_width());
    

如果子类不覆盖未实现的方法,它也是一个抽象类。

界面

在一般计算机科学术语中,接口是向客户端公开的程序部分。公共类和成员是接口的示例。

Java 和 C# 有一个特殊的 interface 关键字。这些或多或少是一个没有实现的抽象类。 (关于常量、嵌套类、显式实现和访问修饰符有一些技巧,我不打算讨论。)虽然关于“无实现”的部分不再适合 Java,但它们添加了默认方法。 interface 关键字可以看作是接口概念的具体化。

回到形状示例

interface Shape 
    def area();  // implicitly abstract so no need for abstract keyword
    def outline_width();  // cannot implement any methods


class Rectangle implements Shape 
    int height = width = 5;
    def override area() =  return height * width; 
    def override outline_width() =  return 1;   // every method in interface must be implemented


def main() = 
    Shape[] shapes =  new Rectangle(), new Oval() ;
    foreach (s in shapes) 
        print("area: " + s.area() + ", outline width: " + s.outline_width());
    

Java 和 C# 不允许对具有实现的类进行多重继承,但它们确实允许实现多个接口。 Java 和 C# 使用接口作为在允许多重继承的语言中发现的 Deadly Diamond of Death Problem 的解决方法(如果处理得当,这并不是那么致命)。

混音

mixin(有时称为特征)允许抽象类的多重继承。 Mixins 没有多重继承所具有的可怕关联(由于 C++ 的疯狂),因此人们更愿意使用它们。它们具有完全相同的致命钻石死亡问题,但支持它们的语言有比 C++ 更优雅的缓解方法,因此它们被认为更好。

Mixins 被誉为与behavioral reuse、more flexible 接口和more powerful 接口的接口。您会注意到所有这些都包含术语interface,指的是Java 和C# 关键字。 混合不是接口。它们是多重继承。有一个更漂亮的名字。

这并不是说 mixins 不好。多重继承也不错。 C++ 解决多重继承的方式是每个人都在讨论的问题。

关于疲惫的、陈旧的 Shape 示例

mixin Shape 
    def abstract area();
    def outline_width() =  return 1; 


class Rectangle with Shape 
    int height = width = 5;
    def override area() =  return height * width; 


def main() = 
    Shape[] shapes =  new Rectangle(), new Oval() ;
    foreach (s in shapes) 
        print("area: " + s.area() + ", outline width: " + s.outline_width());
    

你会注意到这个例子和抽象类例子没有区别。

另外一个花絮是 C# 从 3.0 版开始就支持 mixins。您可以使用接口上的扩展方法来做到这一点。这是具有真实(!)C#代码混合样式的Shape示例

interface Shape

    int Area();


static class ShapeExtensions

    public static int OutlineWidth(this Shape s)
    
        return 1;
    


class Rectangle : Shape

    int height = 5;
    int width = 5;

    public int Area()
    
        return height * width;
    


class Program

    static void Main()
    
        Shape[] shapes = new Shape[] new Rectangle(), new Oval() ;
        foreach (var s in shapes)
        
            Console.Write("area: " + s.Area() + ", outline width: " + s.OutlineWidth());
        
    

【讨论】:

呃,我讨厌更改已接受的答案,但这个答案无疑是这里最好的答案。 明确一点:Mixin\Traits 是在不使用继承的情况下扩展类功能(例如添加方法)的方法。 answear 暗示它们是相同的,但有一些重要的区别。 1. Mixins 可能是有状态的,traits 是无状态的 2. Mixins 使用隐式冲突解决,trait 使用显式冲突解决(更多 ***.com/questions/925609/mixins-vs-traits)【参考方案2】:

由于很多人已经解释了定义和用法,我想只强调重点

界面:

    定义合同(最好是无状态的——我的意思是没有变量) 使用“has a”功能链接不相关的类。 声明公共常量变量(不可变状态)

抽象类:

    在几个密切相关的类之间共享代码。它建立了“is a”关系。

    在相关类之间共享公共状态(状态可以在具体类中修改)

我用一个小例子来结束差异。

Animal 可以是抽象类。 CatDog,扩展这个抽象类建立“is a”关系。

is a动物

is a动物。

can 实现Bark 接口。然后 Dog has a 的吠叫能力。

can 实现Hunt 接口。然后猫has a 狩猎能力。

Man,即not Animal,可以实现Hunt接口。然后 Man has a 狩猎能力。

人和动物(猫/狗)是无关的。但是 Hunt 接口可以为不相关的实体提供相同的功能。

混音:

    如果您想要abstract classinterface 的混合。当你想在许多不相关的类上强制一个新的契约时特别有用,其中一些类必须重新定义新的行为,而其中一些应该坚持共同的实现。在 Mixin 中添加通用实现,并允许其他类在需要时重新定义合约方法

如果我想声明一个抽象类,我会采用这两种方法之一。

    将所有抽象方法移至interface,我的抽象类实现了该接口。

    interface IHunt
        public void doHunting();
    
    abstract class Animal implements IHunt
    
    
    class Cat extends Animal
        public void doHunting()
    
    

相关的 SE 问题:

What is the difference between an interface and abstract class?

【讨论】:

【参考方案3】:

Joshua Bloch 在其有效的 Java 书籍中很好地定义了“混合”的含义。同一本书的摘录:

"mixin 是一种类型 一个类可以实现除了它的“主要类型”来声明它提供 一些可选的行为。例如,Comparable 是一个 mixin 接口,它 允许一个类声明它的实例是相对于其他相互排序的 可比较的对象。这样的接口称为 mixin,因为它允许 可选功能“混合”到类型的主要功能中。"

【讨论】:

【参考方案4】:

对 Java 的引用和提供 mixin 的抽象类的示例具有误导性。 首先,Java 默认不支持“mixins”。在 Java 术语中,抽象类和 Mixins 变得令人困惑。

mixin 是一个类除了它的“主要类型”之外还可以实现的类型,以表明它提供了一些可选的行为。用 Java 术语来说,一个例子就是实现 Serializable 的业务价值对象。

Josh Bloch 说 - “抽象类不能用于定义 mixins - 因为一个类不能有多个父类”(记住 Java 只允许一个“扩展”候选者)

寻找像 Scala 和 Ruby 这样的语言来适当地实现“mixin”的概念

【讨论】:

也是 javascript,使用最广泛的语言,有 mixins 的概念。 java 8版本,接口默认方法提供混合功能。【参考方案5】:

一般:

接口是一个指定操作的合约,但没有任何实现。一些语言(Java、C#)已经内置了对接口的支持,而在另一些语言中,“接口”描述了一种约定,就像 C++ 中的纯虚拟类一样。

抽象类是一个类,它指定了至少一个没有实现的操作。抽象类还可以提供其实现的某些部分。同样,一些语言已经内置支持将类标记为抽象,而在其他语言中它是隐含的。例如,在 C++ 中,定义纯虚方法的类是抽象类。

mixin 是一个类,它旨在使子类中某些功能的实现更容易,但它并非旨在供其自身使用。例如,假设我们有一个用于处理请求的对象的接口

interface RequestHandler 
  void handleRequest(Request request);

也许通过累积请求来缓冲请求直到我们有一些预定的数量然后刷新缓冲区是有用的。我们可以使用 mixin 实现缓冲功能,而无需指定刷新行为:

abstract class BufferedRequestHandlerMixin implements RequestHandler 
  List<Request> buffer = new List<Request>();

  void handleRequest(Request request) 
    buffer.add(request);

    if (buffer.size == BUFFER_FLUSH_SIZE) 
        flushBuffer(buffer);
        buffer.clear();
    
  

  abstract void flushBuffer(List<Request> buffer);

这样,我们很容易编写请求处理程序,将请求写入磁盘、调用 Web 服务等,而无需每次都重写缓冲功能。这些请求处理程序可以简单地扩展BufferedRequestHandlerMixin 并实现flushBuffer

mixin 的另一个很好的例子是 Spring 中的众多支持类之一,即。 HibernateDaoSupport.

【讨论】:

太好了,这正是我需要的,谢谢。但是有一件事:您能否明确说明 mixin 如何“使子类中某些行为的实现更容易”? 如果这个例子有帮助,请告诉我。 等等,也许我遗漏了一些东西,但 BufferedRequestHandlerMixin 不是抽象类吗?这与 Mixin 有何不同? mR_fr0g,是的,BufferedRequestHandlerMixin 是作为抽象类实现的。在 Java 中使用抽象类实现 mixins 很常见,因为 abstract 关键字指出该类是为重用而设计的,并不意味着可以单独使用(它也显然会阻止您单独使用它)。 “抽象类是一个指定至少一个没有实现的操作的类”:对于 .NET 来说并不完全正确。例如,以下是 c# 中的有效类定义:“public abstract class Foo ”【参考方案6】:

抽象类是不是所有成员都实现的类,它们留给继承者实现。它强制其继承者实现其抽象成员。 抽象类不能被实例化,因此它们的构造函数不应该是公共的。]

这是一个 C# 示例:

    public abstract class Employee
    
        protected Employee() 
        public abstract double CalculateSalary(WorkingInfo workingInfo);//no implementation each type of employee should define its salary calculation method.
    

   public class PartTimeEmployee:Employee
  
    private double _workingRate;
    public Employee(double workingRate)
    
     _workingRate=workingRate;
    
    public override double CalculateSalary(WorkingInfo workingInfo)
    
      return workingInfo.Hours*_workingRate;
    

接口是一个类要实现的契约,它只是声明实现类的成员的签名,它本身没有实现。我们通常使用接口来实现多态性,以及解耦依赖的类。

这是一个 C# 示例:

public interface IShape

int Xget;
int Yget;
void Draw();


public class Circle:IShape

public int Xget;set;
public int Yget;set;

public void Draw()

//Draw a circle



public class Rectangle:IShape

public int Xget;set;
public int Yget;set;

public void Draw()

//Draw a rectangle


【讨论】:

【参考方案7】:

基本上,抽象类是具有一些具体实现的接口。接口只是一个没有实现细节的契约。

如果您想在所有实现抽象类的对象中创建通用功能,您将使用抽象类。遵守 OOP 的 DRY(Don't Repeat Yourself)原则。

【讨论】:

不仅如此。例如,抽象类可以定义抽象的受保护方法,而接口则不能。你可以实现任意数量的接口,但你只能扩展一个抽象类(除非你的语言有多重继承)。 是的,肯定比我所描述的要多(对于@musicfreak,您应该进行更多研究,因为关于这个主题有很多)。只是想给 OP 一个快速的定义,因为他说他/她想要一些不那么技术性的东西。 +1 保持简单和重点。另外,n3rd,感谢您提供的信息。

以上是关于抽象类 vs. 接口 vs. mixins的主要内容,如果未能解决你的问题,请参考以下文章

Java中抽象类和接口的区别(abstract class VS interface)

设计模式之美——接口VS抽象类

设计模式学习笔记 接口vs抽象类的区别?是否要为每个类定义接口?

接口与默认的方法VS在Java抽象类8

如何修复 JAVA VS 代码中的实现错误,“必须实现继承的抽象类”

接口和抽象类之间的区别