抽象类与接口






4.76/5 (369投票s)
抽象类与接口:用法和实现。
引言
在本文中,我将连同演示项目一起讨论接口与抽象类。抽象类和接口的概念对于面向对象编程的初学者来说有点令人困惑。因此,我试图讨论这两种概念的理论方面并比较它们的用法。最后,我将演示如何在 C# 中使用它们。
背景
一个没有任何实现的抽象类看起来就像一个接口;然而,它们之间有许多差异而非相似之处。让我们解释这两个概念并比较它们的相似之处和不同之处。
什么是抽象类?
抽象类是一种不能被实例化的特殊类。那么问题来了,为什么我们需要一个不能被实例化的类?抽象类只能被子类化(继承)。换句话说,它只允许其他类继承它,但不能被实例化。其优点是它强制所有子类遵循特定的层次结构。简单来说,它是一种契约,迫使所有子类保持相同的层次结构或标准。
什么是接口?
接口不是类。它是一个由 Interface 关键字定义的实体。接口没有实现;它只有签名,换句话说,只有方法的定义而没有方法体。作为与抽象类的相似之处之一,它是一个用于定义所有子类的层次结构或定义特定方法集及其参数的契约。它们之间的主要区别在于,一个类可以实现多个接口,但只能继承一个抽象类。由于 C# 不支持多重继承,因此接口用于实现多重继承。
两者结合
当我们创建一个接口时,我们基本上是在创建一个没有实现的方法集,这些方法必须被实现的类覆盖。其优点是它为类提供了成为两个类的组成部分的方式:一个来自继承层次结构,一个来自接口。
当我们创建一个抽象类时,我们创建一个基类,该基类可能有一个或多个已完成的方法,但至少有一个或多个方法未完成并声明为 abstract
。如果抽象类的所有方法都未完成,那么它就和接口一样。抽象类的目的是提供一个基类定义,说明一组派生类将如何工作,然后允许程序员在派生类中填充实现。
接口和抽象类之间存在一些相似之处和不同之处,我已经将它们整理成表格以便于比较。
功能 | 接口 | 抽象类 |
多重继承 | 一个类可以继承多个接口。 | 一个类只能继承一个抽象类。 |
默认实现 | 接口不能提供任何代码,只能提供签名。 | 抽象类可以提供完整、默认的代码和/或需要覆盖的详细信息。 |
访问修饰符 | 接口不能为子程序、函数、属性等设置访问修饰符,所有都被假定为 public。 | 抽象类可以包含子程序、函数、属性的访问修饰符。 |
核心 vs 外围 | 接口用于定义类的外围能力。换句话说,人类和车辆都可以继承 IMovable 接口。 | 抽象类定义了类的核心身份,因此它用于同一类型的对象。 |
同质性 | 如果各种实现只共享方法签名,那么最好使用接口。 | 如果各种实现是同一种类并使用共同的行为或状态,那么使用抽象类更好。 |
速度 | 需要更多时间来查找相应类中的实际方法。 | 快速 |
添加功能(版本控制) | 如果我们向接口添加新方法,我们必须跟踪接口的所有实现并为新方法定义实现。 | 如果我们向抽象类添加新方法,我们可以选择提供默认实现,因此所有现有代码可能都能正常工作。 |
字段和常量 | 接口中不能定义字段。 | 抽象类可以定义字段和常量。 |
Using the Code
让我解释一下代码,让它更容易理解。有一个 Employee
抽象类和一个 IEmployee
接口。在抽象类和接口实体中,我将对这些工件之间的差异进行注释。
我正在通过实现它们的对象来测试抽象类和接口。从 Employee
抽象类,我们继承了一个对象:Emp_Fulltime
。同样,从 IEmployee
,我们继承了一个对象:Emp_Fulltime2
。
在 GUI 下方的测试代码中,我创建了 Emp_Fulltime
和 Emp_Fulltime2
的实例,然后设置它们的属性,最后调用对象的 calculateWage
方法。
抽象类 Employee
using System;
namespace AbstractsANDInterfaces
{
///
/// Summary description for Employee.
///
public abstract class Employee
{
//we can have fields and properties
//in the Abstract class
protected String id;
protected String lname;
protected String fname;
//properties
public abstract String ID
{
get;
set;
}
public abstract String FirstName
{
get;
set;
}
public abstract String LastName
{
get;
set;
}
//completed methods
public String Update()
{
return "Employee " + id + " " +
lname + " " + fname +
" updated";
}
//completed methods
public String Add()
{
return "Employee " + id + " " +
lname + " " + fname +
" added";
}
//completed methods
public String Delete()
{
return "Employee " + id + " " +
lname + " " + fname +
" deleted";
}
//completed methods
public String Search()
{
return "Employee " + id + " " +
lname + " " + fname +
" found";
}
//abstract method that is different
//from Fulltime and Contractor
//therefore i keep it uncompleted and
//let each implementation
//complete it the way they calculate the wage.
public abstract String CalculateWage();
}
}
接口 Employee
using System;
namespace AbstractsANDInterfaces
{
/// <summary>
/// Summary description for IEmployee.
/// </summary>
public interface IEmployee
{
//cannot have fields. uncommenting
//will raise error!
// protected String id;
// protected String lname;
// protected String fname;
//just signature of the properties
//and methods.
//setting a rule or contract to be
//followed by implementations.
String ID
{
get;
set;
}
String FirstName
{
get;
set;
}
String LastName
{
get;
set;
}
// cannot have implementation
// cannot have modifiers public
// etc all are assumed public
// cannot have virtual
String Update();
String Add();
String Delete();
String Search();
String CalculateWage();
}
}
继承的对象
Emp_Fulltime
:
using System;
namespace AbstractsANDInterfaces
{
///
/// Summary description for Emp_Fulltime.
///
//Inheriting from the Abstract class
public class Emp_Fulltime : Employee
{
//uses all the properties of the
//Abstract class therefore no
//properties or fields here!
public Emp_Fulltime()
{
}
public override String ID
{
get
{
return id;
}
set
{
id = value;
}
}
public override String FirstName
{
get
{
return fname;
}
set
{
fname = value;
}
}
public override String LastName
{
get
{
return lname;
}
set
{
lname = value;
}
}
//common methods that are
//implemented in the abstract class
public new String Add()
{
return base.Add();
}
//common methods that are implemented
//in the abstract class
public new String Delete()
{
return base.Delete();
}
//common methods that are implemented
//in the abstract class
public new String Search()
{
return base.Search();
}
//common methods that are implemented
//in the abstract class
public new String Update()
{
return base.Update();
}
//abstract method that is different
//from Fulltime and Contractor
//therefore I override it here.
public override String CalculateWage()
{
return "Full time employee " +
base.fname + " is calculated " +
"using the Abstract class...";
}
}
}
Emp_Fulltime2
:
using System;
namespace AbstractsANDInterfaces
{
///
/// Summary description for Emp_fulltime2.
///
//Implementing the interface
public class Emp_fulltime2 : IEmployee
{
//All the properties and
//fields are defined here!
protected String id;
protected String lname;
protected String fname;
public Emp_fulltime2()
{
//
// TODO: Add constructor logic here
//
}
public String ID
{
get
{
return id;
}
set
{
id = value;
}
}
public String FirstName
{
get
{
return fname;
}
set
{
fname = value;
}
}
public String LastName
{
get
{
return lname;
}
set
{
lname = value;
}
}
//all the manipulations including Add,Delete,
//Search, Update, Calculate are done
//within the object as there are not
//implementation in the Interface entity.
public String Add()
{
return "Fulltime Employee " +
fname + " added.";
}
public String Delete()
{
return "Fulltime Employee " +
fname + " deleted.";
}
public String Search()
{
return "Fulltime Employee " +
fname + " searched.";
}
public String Update()
{
return "Fulltime Employee " +
fname + " updated.";
}
//if you change to Calculatewage().
//Just small 'w' it will raise
//error as in interface
//it is CalculateWage() with capital 'W'.
public String CalculateWage()
{
return "Full time employee " +
fname + " caluculated using " +
"Interface.";
}
}
}
测试代码
//This is the sub that tests both
//implementations using Interface and Abstract
private void InterfaceExample_Click(object sender,
System.EventArgs e)
{
try
{
IEmployee emp;
Emp_fulltime2 emp1 = new Emp_fulltime2();
emp = emp1;
emp.ID = "2234";
emp.FirstName= "Rahman" ;
emp.LastName = "Mahmoodi" ;
//call add method od the object
MessageBox.Show(emp.Add().ToString());
//call the CalculateWage method
MessageBox.Show(emp.CalculateWage().ToString());
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void cmdAbstractExample_Click(object sender,
System.EventArgs e)
{
Employee emp;
emp = new Emp_Fulltime();
emp.ID = "2244";
emp.FirstName= "Maria" ;
emp.LastName = "Robinlius" ;
MessageBox.Show(emp.Add().ToString());
//call the CalculateWage method
MessageBox.Show(emp.CalculateWage().ToString());
}
结论
在上面的例子中,我解释了抽象类和接口之间的区别。我还实现了一个使用抽象类和接口的演示项目,并展示了它们在实现上的差异。