面向对象进阶 - 抽象类

27 阅读5分钟

专栏导航

在上一篇文章中,我们学习了继承。这一周,我们将探索抽象类的概念。抽象类是面向对象编程中更高级的特性,它为我们提供了定义通用模板的能力,让代码更加灵活和可扩展。

一、什么是抽象类?

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 ✅ 推荐做法

  1. 设计良好的抽象类
abstract class Animal
{
    // 抽象方法:定义契约
    public abstract void MakeSound();

    // 普通方法:提供通用实现
    public void Sleep()
    {
        Console.WriteLine("睡觉...");
    }
}
  1. 使用抽象类提供模板
abstract class ReportGenerator
{
    // 模板方法
    public void GenerateReport()
    {
        CollectData();
        ProcessData();
        OutputReport();
    }

    // 抽象方法:派生类实现
    protected abstract void CollectData();
    protected abstract void ProcessData();
    protected abstract void OutputReport();
}

8.2 ❌ 不推荐做法

  1. 不要创建空的抽象类
// ❌ 空的抽象类,应该使用接口
abstract class EmptyAbstract
{
}
  1. 不要滥用抽象类
// ❌ 只有具体类,不需要抽象
abstract class SimpleClass
{
    public void SimpleMethod()
    {
        Console.WriteLine("简单方法");
    }
}

本章总结

  • ✅ 理解了抽象类的概念
  • ✅ 学会了使用 abstract 关键字
  • ✅ 掌握了抽象方法的定义和实现
  • ✅ 理解了抽象类 vs 普通类的区别
  • ✅ 实践了完整的支付系统和数据存储系统