今天来梳理一下基类、抽象类、接口不同类型变量和子类实例对象之间的关系。话不多说直接开始。
基本功能
- 基类是具备完整功能的普通类,可直接创建实例。当基类有可复用的功能时,子类可直接继承,其主要作用是复用已有的变量、属性、方法。
- 抽象类是不完整的模版类,不可以被直接创建实例。抽象类内部存在未被实现的抽象方法,子类继承抽象类后,需强制子类实现抽象方法。与普通类一样,抽象类同样存在可复用的变量、属性、方法。其主要作用是通过抽象方法约束子类结构,并同时拥有复用通用功能的能力。
- 接口是纯方法定义,无任何功能的具体实现(C# 8.0 + 可加默认实现,但不推荐)。接口无法被实例化,需要继承后实现约定的方法。其主要作用是约定方法,并可以解除类的依赖关系,接口可以实现多重继承。
使用场景
基类、抽象类、接口都可以由子类继承,三者都可以由各自类型的引用变量持有子类实例。示例如下:
cs
public class Program
{
static void Main(string[] args)
{
BaseClass baseClass = new DerivedClass();
AbstractClass abstractClass = new AbstractDerivedClass();
Interface @interface = new InterfaceDerivedClass();
Console.ReadKey();
}
}
接下来就详细说明一下各自的区别和使用场景。
基类
基类是完整的普通了,封装了独立的变量、属性、方法。当子类继承基类时,可直接拥有基类的这些功能。示例如下:
cs
public class Program
{
static void Main(string[] args)
{
Animal dog = new Dog();
dog.Call(); //输出:动物叫
Animal cat = new Cat();
cat.Call(); //输出:动物叫
Console.ReadKey();
}
}
public class Animal
{
public void Call()
{
Console.WriteLine("动物叫");
}
}
public class Dog : Animal
{
}
public class Cat : Animal
{
}
当基类方法标记为virtual时,子类可重写基类方法。虽然变量类型为基类(Animal),但创建出来的实例为子类(Dog、Cat),因此在方法的实际调用时,调用的是子类中重写的方法。示例代码如下:
cs
public class Program
{
static void Main(string[] args)
{
Animal dog = new Dog();
dog.Call(); //输出:汪汪汪
Animal cat = new Cat();
cat.Call(); //输出:喵喵喵
Console.ReadKey();
}
}
public class Animal
{
public virtual void Call()
{
Console.WriteLine("动物叫");
}
}
public class Dog : Animal
{
public override void Call()
{
Console.WriteLine("汪汪汪");
}
}
public class Cat : Animal
{
public override void Call()
{
Console.WriteLine("喵喵喵");
}
}
通过上面的示例可以看出,可以通过基类类型统一的持有不同的子类类型,并调用子类中重写的方法,子类重写并非强制。
通过这种特性,我们可以通过基类类型统一的管理不同的子类实例。如数组、方法参数等。示例如下:
cs
public class Program
{
static void Main(string[] args)
{
Animal[] animals = { new Dog(), new Cat() };
for (int i=0;i<animals.Length;i++)
{
animals[i].Call();
}
Console.ReadKey();
}
}
抽象类
抽象类在功能继承方面与基类相同,抽象类同样可以提供变量、属性、方法等完整的封装功能。抽象类的核心区别在于,可使用abstract 关键字标记抽象方法,抽象方法需在子类中强制实现,是一种强制性约定。由于抽象方法在基类未被实现,所以抽象类不能被实例化。
示例如下:
cs
public class Program
{
static void Main(string[] args)
{
Animal[] animals = { new Dog(), new Cat() };
for (int i = 0; i < animals.Length; i++)
{
animals[i].Call();
}
Console.ReadKey();
}
}
public abstract class Animal
{
public abstract void Call();
}
public class Dog : Animal
{
public override void Call()
{
Console.WriteLine("汪汪汪");
}
}
public class Cat : Animal
{
public override void Call()
{
Console.WriteLine("喵喵喵");
}
}
抽象类同样可以用基类变量引用子类实例,即需要基类的功能继承,又需要与子类强制约定方法时,可采用抽象类。
接口
接口是纯方法定义,无任何功能实现。接口同样无法被实例化。接口的核心作用在于为不同的子类约定了接口,这些接口必须在子类中实现。示例如下:
cs
public class Program
{
static void Main(string[] args)
{
IAnimal[] animals = { new Dog(), new Cat() };
for (int i = 0; i < animals.Length; i++)
{
animals[i].Call();
}
Console.ReadKey();
}
}
public interface IAnimal
{
public void Call();
}
public class Dog : IAnimal
{
public void Call()
{
Console.WriteLine("汪汪汪");
}
}
public class Cat : IAnimal
{
public void Call()
{
Console.WriteLine("喵喵喵");
}
}
与基类、抽象类相同,同样可以用接口类型变量引用子类实例,提供了统一的持有变量。不同在于接口类内部只能定义方法和属性,不能持有变量。
归纳总结
- **基类:**可实例化,有自己的变量、属性、方法,完整的独立功能。子类可复用其功能,对子类无强约束。只在功能继承时选择使用。
- **抽象类:**不可实例化,有自己的变量、属性、方法,完整的独立功能。子类可复用其功能,对子类有强制方法约定,在子类中实现约定方法。在需要功能继承,且需要强制约定子类接口时选择使用。
- **接口:**不可实例化,只做接口约定,无变量和方法实现。子类无复用功能,强制约定子类的方法,在子类中实现约定方法。在无需功能继承,需强制约定子类接口时选择使用。