方法重写(Method Overriding)是 C# 面向对象编程中的一个核心概念,它允许派生类根据需要改变继承自基类的方法实现。通过方法重写,可以实现多态性,使程序更加灵活、易于扩展和维护。本文将详细介绍方法重写的特性和应用场景,并通过多个实例加深对这一概念的理解。
实现项目中方法重写比较常用。
方法重写的特性
多态性
- 定义:多态性允许相同的接口调用,根据对象的实际类型执行不同的操作。
- 实现:通过在基类中定义虚方法(virtual),在派生类中重写(override)这些方法来实现多态。
灵活性
- 重写基类的方法,派生类可以根据自身需求修改或扩展方法的实现,而无需修改基类的代码。
可维护性
- 通过方法重写,可以在不改变基类代码的情况下,对其行为进行修改或扩展,增强了代码的可维护性和可扩展性。
方法重写的应用场景
定制化行为
当派生类需要提供与基类不同的具体实现时,可通过重写基类的方法来实现定制化行为。
扩展基类功能
在不改变基类的情况下,派生类可以通过调用基类方法并添加新的功能来扩展基类的行为。
实现设计模式
某些设计模式(如模板方法模式)需要通过在派生类中重写基类方法来实现特定的业务逻辑。
方法重写的语法与规则
- 基类方法:使用 virtual 关键字标识可被重写的方法。
public virtual void MethodName()
{
// 基类方法实现
}
- 派生类方法:使用 override 关键字重写基类的方法。
public override void MethodName()
{
// 派生类方法实现
}
- 调用基类方法:在重写的方法中,可以使用 base.MethodName() 调用基类的方法实现。
实例解析
示例 1:动物叫声
目标:演示如何通过方法重写实现多态性。
namespace App06
{
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("动物发出声音。");
}
}
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("狗叫:汪汪!");
}
}
public class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("猫叫:喵喵!");
}
}
internal class Program
{
static void Main(string[] args)
{
// 使用示例
Animal[] animals = { new Dog(), new Cat(), new Animal() };
foreach (var animal in animals)
{
animal.Speak();
}
}
}
}
说明:
- 基类 Animal 定义了虚方法 Speak()。
- Dog 和 Cat 类重写了 Speak() 方法,实现各自的叫声。
- 通过基类引用调用 Speak() 方法,根据实际对象类型执行不同的实现。
示例 2:车辆启动
目标:展示如何在重写的方法中调用基类的方法。
namespace App06
{
public class Vehicle
{
public virtual void Start()
{
Console.WriteLine("车辆启动。");
}
}
public class Car : Vehicle
{
public override void Start()
{
base.Start(); // 调用基类的 Start 方法
Console.WriteLine("汽车发动引擎。");
}
}
internal class Program
{
static void Main(string[] args)
{
// 使用示例
Vehicle myCar = new Car();
myCar.Start();
}
}
}
说明:
- Car 类重写了 Start() 方法,并使用 base.Start() 调用了基类的方法,实现了功能的扩展。
示例 3:图形绘制
目标:演示抽象类和方法重写的结合使用。
namespace App06
{
public abstract class Shape
{
public abstract void Draw();
}
public class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("绘制一个圆形。");
}
}
public class Rectangle : Shape
{
public override void Draw()
{
Console.WriteLine("绘制一个矩形。");
}
}
internal class Program
{
static void Main(string[] args)
{
// 使用示例
List<Shape> shapes = new List<Shape>
{
new Circle(),
new Rectangle()
};
foreach (var shape in shapes)
{
shape.Draw();
}
}
}
}
说明:
- 抽象类 Shape 定义了抽象方法 Draw(),强制派生类必须实现该方法。
- 派生类 Circle 和 Rectangle 实现了各自的 Draw() 方法。
示例 4:员工薪资计算
目标:通过方法重写计算不同类型员工的薪资。
namespace App06
{
public class Employee
{
public virtual double CalculateSalary()
{
return 3000.0; // 基本工资
}
}
public class Manager : Employee
{
public override double CalculateSalary()
{
return base.CalculateSalary() + 2000.0; // 基本工资 + 管理奖金
}
}
public class Salesperson : Employee
{
public double SalesAmount { get; set; }
public override double CalculateSalary()
{
return base.CalculateSalary() + SalesAmount * 0.05; // 基本工资 + 销售提成
}
}
internal class Program
{
static void Main(string[] args)
{
// 使用示例
Employee emp1 = new Manager();
Employee emp2 = new Salesperson { SalesAmount = 50000 };
Console.WriteLine($"经理薪资:{emp1.CalculateSalary()}");
Console.WriteLine($"销售员薪资:{emp2.CalculateSalary()}");
}
}
}
说明:
- Manager 和 Salesperson 重写了 CalculateSalary() 方法,分别计算不同的薪资。
- 使用基类引用调用方法,实现了对不同员工薪资的多态计算。
示例 5:模板方法模式
目标:通过方法重写实现模板方法模式。
namespace App06
{
public abstract class DataProcessor
{
// 模板方法
public void ProcessData()
{
ReadData();
Process();
SaveData();
}
protected abstract void ReadData();
protected abstract void Process();
protected virtual void SaveData()
{
Console.WriteLine("数据已保存。");
}
}
public class ExcelDataProcessor : DataProcessor
{
protected override void ReadData()
{
Console.WriteLine("从 Excel 文件读取数据。");
}
protected override void Process()
{
Console.WriteLine("处理 Excel 数据。");
}
}
public class CsvDataProcessor : DataProcessor
{
protected override void ReadData()
{
Console.WriteLine("从 CSV 文件读取数据。");
}
protected override void Process()
{
Console.WriteLine("处理 CSV 数据。");
}
protected override void SaveData()
{
Console.WriteLine("数据已保存到 CSV 文件。");
}
}
internal class Program
{
static void Main(string[] args)
{
// 使用示例
DataProcessor processor1 = new ExcelDataProcessor();
processor1.ProcessData();
DataProcessor processor2 = new CsvDataProcessor();
processor2.ProcessData();
}
}
}
图片
说明:
- 基类 DataProcessor 定义了模板方法 ProcessData(),并声明了一些抽象方法。
- 派生类实现了这些抽象方法,定制了具体的处理步骤。
注意事项
- 方法签名一致:重写的方法必须与基类的方法具有相同的签名,包括方法名、参数类型和返回类型。
- 访问修饰符:重写的方法的访问级别不能低于基类中被重写的方法。
- sealed 关键字:如果不希望派生类进一步重写某个方法,可以在方法前添加 sealed 关键字。
public override sealed void MethodName()
{
// 实现
}
总结
方法重写是实现多态性的关键,可以使代码更加灵活和可维护。通过在派生类中重写基类的方法,我们可以根据需要改变或扩展基类的行为,而无需修改基类的代码。在设计类的继承结构时,合理地使用方法重写,可以提高代码的质量和可扩展性。
建议:
- 在基类中,预期可能被修改的方法应声明为 virtual 或 abstract。
- 在派生类中,重写方法时确保方法签名正确,并考虑是否需要调用 base 方法。
- 谨慎使用方法重写,避免造成代码的混乱和难以维护。