专栏导航
- ← 上一篇:面向对象进阶 - 继承
- ← 第一篇:编程世界初探
- 专栏目录
在上一篇文章中,我们学习了继承。这一周,我们将探索抽象类的概念。抽象类是面向对象编程中更高级的特性,它为我们提供了定义通用模板的能力,让代码更加灵活和可扩展。
一、什么是抽象类?
1.1 现实生活中的抽象
想象一下:
"图形"是一个抽象概念
├── 圆形 - 具体图形
├── 矩形 - 具体图形
└── 三角形 - 具体图形
你不能说"给我画一个图形",必须指定是圆形、矩形还是三角形。图形是一个抽象的概念。
1.2 编程中的抽象类
抽象类(Abstract Class)是一种特殊的类,它不能被实例化,只能被继承。抽象类通常包含抽象方法(没有实现的方法)和普通方法。
抽象类(Shape)
├── 具体类(Circle)
├── 具体类(Rectangle)
└── 具体类(Triangle)
1.3 为什么要使用抽象类?
好处:
✅ 强制实现:派生类必须实现抽象方法
✅ 代码复用:在基类中提供公共实现
✅ 设计规范:为派生类定义统一的接口
✅ 灵活扩展:易于添加新的派生类
二、abstract 关键字
2.1 基本语法
// 抽象类
abstract class 类名
{
// 抽象方法:没有方法体
public abstract 返回类型 方法名(参数);
// 普通方法:可以有实现
public void 普通方法()
{
// 方法体
}
}
// 具体类:必须实现所有抽象方法
class 具体类 : 抽象类
{
public override 返回类型 方法名(参数)
{
// 实现方法
}
}
2.2 抽象类的特点
| 特点 | 说明 |
|---|---|
| 不能实例化 | new Shape() 是错误的 |
| 可以包含抽象方法 | 方法没有实现 |
| 可以包含普通方法 | 方法有实现 |
| 派生类必须实现所有抽象方法 | 除非派生类也是抽象类 |
| 可以包含属性、字段等 | 和普通类一样 |
三、抽象类示例
3.1 简单示例:图形类
using System;
namespace Week10Practice
{
// 抽象类:图形
abstract class Shape
{
// 抽象方法:计算面积
public abstract double CalculateArea();
// 抽象方法:计算周长
public abstract double CalculatePerimeter();
// 普通方法:显示信息
public void DisplayInfo()
{
Console.WriteLine($"形状: {GetType().Name}");
Console.WriteLine($"面积: {CalculateArea():F2}");
Console.WriteLine($"周长: {CalculatePerimeter():F2}");
}
}
// 具体类:圆形
class Circle : Shape
{
public double Radius { get; set; }
public Circle(double radius)
{
Radius = radius;
}
// 必须实现抽象方法
public override double CalculateArea()
{
return Math.PI * Radius * Radius;
}
public override double CalculatePerimeter()
{
return 2 * Math.PI * Radius;
}
}
// 具体类:矩形
class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public Rectangle(double width, double height)
{
Width = width;
Height = height;
}
public override double CalculateArea()
{
return Width * Height;
}
public override double CalculatePerimeter()
{
return 2 * (Width + Height);
}
}
// 具体类:三角形
class Triangle : Shape
{
public double Base { get; set; }
public double Height { get; set; }
public double SideA { get; set; }
public double SideB { get; set; }
public double SideC { get; set; }
public Triangle(double @base, double height, double sideA, double sideB, double sideC)
{
Base = @base;
Height = height;
SideA = sideA;
SideB = sideB;
SideC = sideC;
}
public override double CalculateArea()
{
return 0.5 * Base * Height;
}
public override double CalculatePerimeter()
{
return SideA + SideB + SideC;
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("===== 抽象类示例:图形 =====\n");
// 创建具体对象
Shape[] shapes = new Shape[]
{
new Circle(5),
new Rectangle(4, 6),
new Triangle(6, 4, 5, 5, 6)
};
foreach (Shape shape in shapes)
{
shape.DisplayInfo();
Console.WriteLine();
}
// ❌ 不能创建抽象类的实例
// Shape shape = new Shape(); // 编译错误
}
}
}
输出:
===== 抽象类示例:图形 =====
形状: Circle
面积: 78.54
周长: 31.42
形状: Rectangle
面积: 24.00
周长: 20.00
形状: Triangle
面积: 12.00
周长: 16.00
3.2 抽象类 vs 普通类
// 普通类
class Person
{
public string Name { get; set; }
public virtual void Speak()
{
Console.WriteLine($"{Name} 说话");
}
}
// 抽象类
abstract class Animal
{
public string Name { get; set; }
public void Eat() // 普通方法
{
Console.WriteLine($"{Name} 吃东西");
}
public abstract void Speak(); // 抽象方法:必须被重写
}
// 具体类
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("汪汪!");
}
}
class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("喵喵!");
}
}
四、抽象方法
4.1 抽象方法的特点
- 没有方法体(没有大括号)
- 必须在抽象类中声明
- 必须被派生类实现(除非派生类也是抽象类)
- 使用
override关键字重写
abstract class PaymentMethod
{
// 抽象方法:没有实现
public abstract bool ProcessPayment(decimal amount);
// 普通方法:有实现
public void LogTransaction(decimal amount)
{
Console.WriteLine($"记录交易: {amount:C}");
}
}
4.2 完整示例:支付系统
using System;
namespace Week10Practice
{
// 抽象类:支付方式
abstract class PaymentMethod
{
public string Name { get; set; }
// 抽象方法:处理支付
public abstract bool ProcessPayment(decimal amount);
// 抽象方法:验证
public abstract bool Validate();
// 普通方法:记录日志
protected void LogTransaction(decimal amount, bool success)
{
if (success)
{
Console.WriteLine($"✅ {Name} 支付成功: {amount:C}");
}
else
{
Console.WriteLine($"❌ {Name} 支付失败: {amount:C}");
}
}
}
// 具体类:信用卡支付
class CreditCardPayment : PaymentMethod
{
public string CardNumber { get; set; }
public string CardHolder { get; set; }
public DateTime ExpiryDate { get; set; }
public CreditCardPayment(string cardNumber, string cardHolder, DateTime expiryDate)
{
Name = "信用卡";
CardNumber = cardNumber;
CardHolder = cardHolder;
ExpiryDate = expiryDate;
}
public override bool Validate()
{
// 简单验证:检查卡号长度和有效期
if (CardNumber.Length != 16)
{
Console.WriteLine("卡号格式错误!");
return false;
}
if (ExpiryDate < DateTime.Now)
{
Console.WriteLine("卡片已过期!");
return false;
}
return true;
}
public override bool ProcessPayment(decimal amount)
{
if (!Validate())
{
LogTransaction(amount, false);
return false;
}
// 模拟支付处理
LogTransaction(amount, true);
return true;
}
}
// 具体类:支付宝支付
class AlipayPayment : PaymentMethod
{
public string AccountNumber { get; set; }
public string Password { get; set; }
public AlipayPayment(string accountNumber, string password)
{
Name = "支付宝";
AccountNumber = accountNumber;
Password = password;
}
public override bool Validate()
{
// 简单验证
if (string.IsNullOrEmpty(AccountNumber) || AccountNumber.Length < 6)
{
Console.WriteLine("账号格式错误!");
return false;
}
if (string.IsNullOrEmpty(Password))
{
Console.WriteLine("密码不能为空!");
return false;
}
return true;
}
public override bool ProcessPayment(decimal amount)
{
if (!Validate())
{
LogTransaction(amount, false);
return false;
}
// 模拟支付处理
LogTransaction(amount, true);
return true;
}
}
// 具体类:微信支付
class WeChatPayment : PaymentMethod
{
public string OpenId { get; set; }
public WeChatPayment(string openId)
{
Name = "微信支付";
OpenId = openId;
}
public override bool Validate()
{
if (string.IsNullOrEmpty(OpenId) || OpenId.Length < 10)
{
Console.WriteLine("OpenId 格式错误!");
return false;
}
return true;
}
public override bool ProcessPayment(decimal amount)
{
if (!Validate())
{
LogTransaction(amount, false);
return false;
}
// 模拟支付处理
LogTransaction(amount, true);
return true;
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("===== 支付系统 =====\n");
List<PaymentMethod> paymentMethods = new List<PaymentMethod>
{
new CreditCardPayment("1234567890123456", "张三", DateTime.Now.AddMonths(12)),
new AlipayPayment("zhangsan123", "password123"),
new WeChatPayment("wx_zhangsan_123456789")
};
decimal amount = 299.99m;
foreach (PaymentMethod payment in paymentMethods)
{
Console.WriteLine($"--- 使用 {payment.Name} 支付 ---");
payment.ProcessPayment(amount);
Console.WriteLine();
}
}
}
}
输出:
===== 支付系统 =====
--- 使用 信用卡 支付 ---
✅ 信用卡 支付成功: ¥299.99
--- 使用 支付宝 支付 ---
✅ 支付宝 支付成功: ¥299.99
--- 使用 微信支付 支付 ---
✅ 微信支付 支付成功: ¥299.99
五、抽象类中的成员
5.1 可以包含的成员
抽象类可以包含:
| 成员类型 | 是否可以 | 说明 |
|---|---|---|
| 抽象方法 | ✅ | 必须在派生类中实现 |
| 普通方法 | ✅ | 可以被直接使用或重写 |
| 虚方法 | ✅ | 可以在派生类中重写 |
| 属性 | ✅ | 可以是抽象的或普通的 |
| 字段 | ✅ | 和普通类一样 |
| 构造函数 | ✅ | 用于初始化抽象类 |
5.2 抽象属性示例
abstract class Vehicle
{
// 抽象属性
public abstract string Brand { get; set; }
public abstract int MaxSpeed { get; }
// 普通属性
public int CurrentSpeed { get; protected set; }
// 抽象方法
public abstract void Accelerate();
public abstract void Brake();
// 普通方法
public void DisplaySpeed()
{
Console.WriteLine($"当前速度: {CurrentSpeed} km/h");
}
}
class Car : Vehicle
{
public override string Brand { get; set; }
public override int MaxSpeed { get; }
public Car(string brand, int maxSpeed)
{
Brand = brand;
MaxSpeed = maxSpeed;
}
public override void Accelerate()
{
if (CurrentSpeed < MaxSpeed)
{
CurrentSpeed += 10;
Console.WriteLine($"{Brand} 加速中...");
}
else
{
Console.WriteLine($"{Brand} 已达到最高速度!");
}
}
public override void Brake()
{
if (CurrentSpeed > 0)
{
CurrentSpeed -= 10;
Console.WriteLine($"{Brand} 刹车中...");
}
else
{
Console.WriteLine($"{Brand} 已停止!");
}
}
}
六、抽象类 vs 接口(预告)
虽然接口和抽象类很相似,但它们有重要的区别:
| 特性 | 抽象类 | 接口 |
|---|---|---|
| 实现 | 可以包含部分实现 | 不能包含实现 |
| 继承 | 单继承 | 可以多继承 |
| 访问修饰符 | 可以使用各种修饰符 | 默认 public |
| 字段 | 可以有字段 | 不能有字段 |
| 构造函数 | 可以有构造函数 | 不能有构造函数 |
抽象类更适合:
- 需要提供部分实现
- 建立 IS-A 关系(是一个)
- 需要共享代码
接口更适合:
- 定义契约/规范
- 建立 CAN-DO 关系(能够做)
- 多重继承场景
(接口将在第11章详细讲解)
七、实践示例:数据存储系统
using System;
using System.Collections.Generic;
namespace Week10Practice
{
// 抽象类:数据存储
abstract class DataStorage
{
public string Name { get; set; }
// 抽象方法:必须实现
public abstract void Save(string key, string value);
public abstract string Load(string key);
public abstract void Delete(string key);
// 普通方法:通用实现
public void DisplayInfo()
{
Console.WriteLine($"存储方式: {Name}");
}
// 模板方法:定义算法骨架
public bool SafeSave(string key, string value)
{
try
{
if (string.IsNullOrEmpty(key))
{
Console.WriteLine("键不能为空!");
return false;
}
Save(key, value);
Console.WriteLine($"成功保存: {key}");
return true;
}
catch (Exception ex)
{
Console.WriteLine($"保存失败: {ex.Message}");
return false;
}
}
}
// 具体类:内存存储
class MemoryStorage : DataStorage
{
private Dictionary<string, string> storage = new Dictionary<string, string>();
public MemoryStorage()
{
Name = "内存存储";
}
public override void Save(string key, string value)
{
storage[key] = value;
}
public override string Load(string key)
{
if (storage.ContainsKey(key))
{
return storage[key];
}
return null;
}
public override void Delete(string key)
{
storage.Remove(key);
}
}
// 具体类:文件存储
class FileStorage : DataStorage
{
private string basePath = "data/";
public FileStorage()
{
Name = "文件存储";
}
public override void Save(string key, string value)
{
string filePath = basePath + key + ".txt";
System.IO.File.WriteAllText(filePath, value);
}
public override string Load(string key)
{
string filePath = basePath + key + ".txt";
if (System.IO.File.Exists(filePath))
{
return System.IO.File.ReadAllText(filePath);
}
return null;
}
public override void Delete(string key)
{
string filePath = basePath + key + ".txt";
if (System.IO.File.Exists(filePath))
{
System.IO.File.Delete(filePath);
}
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("===== 数据存储系统 =====\n");
List<DataStorage> storages = new List<DataStorage>
{
new MemoryStorage(),
new FileStorage()
};
string key = "user_data";
string value = "张三, 25, 北京";
foreach (DataStorage storage in storages)
{
Console.WriteLine($"--- {storage.Name} ---");
storage.DisplayInfo();
storage.SafeSave(key, value);
string loaded = storage.Load(key);
if (loaded != null)
{
Console.WriteLine($"加载数据: {loaded}");
}
else
{
Console.WriteLine("数据不存在!");
}
Console.WriteLine();
}
}
}
}
输出:
===== 数据存储系统 =====
--- 内存存储 ---
存储方式: 内存存储
成功保存: user_data
加载数据: 张三, 25, 北京
--- 文件存储 ---
存储方式: 文件存储
成功保存: user_data
加载数据: 张三, 25, 北京
八、使用抽象类的最佳实践
8.1 ✅ 推荐做法
- 设计良好的抽象类
abstract class Animal
{
// 抽象方法:定义契约
public abstract void MakeSound();
// 普通方法:提供通用实现
public void Sleep()
{
Console.WriteLine("睡觉...");
}
}
- 使用抽象类提供模板
abstract class ReportGenerator
{
// 模板方法
public void GenerateReport()
{
CollectData();
ProcessData();
OutputReport();
}
// 抽象方法:派生类实现
protected abstract void CollectData();
protected abstract void ProcessData();
protected abstract void OutputReport();
}
8.2 ❌ 不推荐做法
- 不要创建空的抽象类
// ❌ 空的抽象类,应该使用接口
abstract class EmptyAbstract
{
}
- 不要滥用抽象类
// ❌ 只有具体类,不需要抽象
abstract class SimpleClass
{
public void SimpleMethod()
{
Console.WriteLine("简单方法");
}
}
本章总结
- ✅ 理解了抽象类的概念
- ✅ 学会了使用 abstract 关键字
- ✅ 掌握了抽象方法的定义和实现
- ✅ 理解了抽象类 vs 普通类的区别
- ✅ 实践了完整的支付系统和数据存储系统