CodeGym /课程 /C# SELF /抽象类和方法

抽象类和方法

C# SELF
第 22 级 , 课程 1
可用

1. 入门

抽象类 —— 就是你不能直接用new来创建的类。它的作用是让你在它的基础上写出更具体的类。可以把它当成一种模板或者“蓝图”,它规定了子类都要有啥结构和行为,但具体细节让子类自己去实现。

来个比喻更好懂:假如你有个“交通工具”的概念。现实生活中没人骑着抽象的“交通工具”到处跑(除非你在脑补)。有人开车,有人坐船,有人坐飞机。抽象类就是你的“交通工具”蓝图,它规定了所有交通工具都得有个核心功能——能移动(Move),但怎么移动,得让子类自己说了算。


// 抽象类示例
public abstract class Transport
{
    public string Model { get; set; }

    // 抽象方法——没有实现
    public abstract void Move();
}

你要是想创建Transport类型的对象,编译器会直接报错!你可以试试,编译器会很快告诉你:“不能创建抽象类的实例”。

什么时候用抽象类?为啥要用?

  • 想把一堆相似对象的共性和行为都集中到一个地方。
  • 需要定个“统一标准”:比如“所有子类都得能移动”。
  • 有些方法可以写通用实现,但有些逻辑必须让子类自己补充。

抽象类是设计大型系统架构的神器,适合那种有一堆通用“功能”,但每个子类又有自己特殊实现的场景。

2. 抽象类和普通类的区别

这里经常有人搞混,咱们来理一理!

抽象类 vs 普通类

  • 普通类可以直接用new创建对象,所有方法都能有实现。
  • 抽象类
    • 不能直接创建(类声明里有abstract
    • 可以有实现的方法,也可以有没实现(没方法体)的抽象方法
    • 可以有字段、属性、构造函数,甚至静态方法

public abstract class Animal
{
    public string Name { get; set; }
    public void Sleep()
    {
        Console.WriteLine("动物在睡觉!");
    }

    public abstract void MakeSound();
}

3. 抽象方法和属性

抽象方法

抽象方法——只能在抽象类里声明,没有方法体。子类必须自己实现它,怎么写随你。


public abstract class Animal
{
    public string Name { get; set; }

    // 抽象方法
    public abstract void MakeSound();
}

抽象属性

你也可以声明抽象属性,这样所有子类都必须实现它:


public abstract class Shape
{
    // 抽象属性
    public abstract double Area { get; }
}

4. 继承抽象类

抽象类可以有抽象方法,也可以有实现的方法。子类必须实现所有抽象方法和属性,否则它自己也得是抽象类。

咱们来扩展下之前讲过的“动物应用”:


public abstract class Animal
{
    public string Name { get; set; }

    public void Sleep()
    {
        Console.WriteLine($"{Name} 睡得很香!");
    }

    public abstract void MakeSound();
}

现在写个普通(非抽象)类:


public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine($"{Name} 说:汪!");
    }
}

你要是不实现MakeSound(),编译器会报错。

图解:带抽象类的类继承结构


+---------------------+
|   Animal (abstract) |
+---------------------+
| Name: string        |
| + Sleep()           |
| + MakeSound()*      |
+---------------------+
         ^
         |
  +------+-------+
  |              |
 Dog            Cat

* MakeSound() —— 抽象方法

5. 多态实战:抽象怎么用在实际项目里

来,咱们在小项目里用用这些知识。假设你有个动物列表——比如有狗和猫。


List<Animal> animals = new List<Animal>
{
    new Dog { Name = "Rex" },
    new Cat { Name = "Luna" }
};

foreach (var animal in animals)
{
    animal.MakeSound(); // 多态调用:各自实现
    animal.Sleep();     // 调用基类实现的方法
}

Cat类实现也差不多:


public class Cat : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine($"{Name} 说:喵!");
    }
}

实际项目里的好处

抽象类在大型应用架构里用得超多。比如画图软件里通常有个抽象类Shape,有Draw()方法和Area属性。具体的图形(比如矩形、圆形)用自己的公式实现这些抽象方法。这样你可以随便加新图形,程序逻辑不用重写一遍。

6. 抽象类的特点和限制

不能创建实例

编译器绝对不让你这么干:


Animal a = new Animal(); // 错误!

只有子类(具体类)能被实例化。

抽象类可以包含:

  • 抽象方法声明(没实现)
  • 实现的方法(有方法体)
  • 属性和字段(可以是抽象的,也可以有实现)
  • 构造函数——没错,抽象类也能有构造函数,但只能被子类调用,不能直接new出来用。

抽象类支持继承

你可以搞一整套抽象层级:


public abstract class Creature
{
    public abstract void Live();
}

public abstract class Animal : Creature
{
    public abstract override void Live();
    public abstract void MakeSound();
}

如果继承链上有类没实现所有抽象方法,它自己也得声明成抽象类!

7. 抽象类和构造函数

抽象类可以有构造函数,这些构造函数会在子类构造函数里被调用。
重点:抽象类的构造函数一般声明成protected,这样就明确只能被子类用,不能直接实例化。


public abstract class Animal
{
    public string Name { get; set; }
    
    // 构造函数声明为protected
    protected Animal(string name)
    {
        Name = name;
        Console.WriteLine($"创建了名叫{name}的动物");
    }
    
    public abstract void MakeSound();
}

public class Dog : Animal
{
    public Dog(string name) : base(name)
    {
        // base(name)会调用Animal的构造函数
        Console.WriteLine("创建了一只狗");
    }

    public override void MakeSound()
    {
        Console.WriteLine($"{Name} 说:汪!");
    }
}

为啥用protected构造函数?

  • 意图明确:protected表示构造函数只给子类用
  • 防止误用:虽然编译器本来也不让你new,但protected构造函数让API更清晰,别人一看就懂
  • 好习惯:符合封装和API设计原则

你也可以用public构造函数,但protected更能表达它的用途。

8. 抽象方法和虚方法的对比

抽象方法其实就是特殊的虚方法(它们本质上总是virtual,虽然不用写这个词),但抽象方法不能有实现。如果你想让子类可以重写(但不是必须),用virtual。如果必须重写——就用abstract


public abstract class Worker
{
    // 必须实现
    public abstract void Work();

    // 可以重写,也可以用默认实现
    public virtual void Rest()
    {
        Console.WriteLine("标准休息!");
    }
}
抽象方法 虚方法
基类有没有实现 没有,只有声明(没方法体) 有,默认实现
能在哪声明 只能在抽象类里 任何非sealed类,包括抽象类
子类必须重写吗 第一个非抽象子类必须重写 不一定。可以重写,也可以用基类实现
能不能直接调用 不能(没方法体) 能(会执行基类版本)
主要用途 强制子类写自己的实现。定义“标准” 让子类有机会改行为,按需重写

9. 抽象类在你项目里的用法

继续完善我们的学习项目。现在我们需要一个处理用户输入的抽象类(比如各种表单)。


public abstract class InputForm
{
    public string Title { get; set; }

    // 抽象方法,实现输入处理流程
    public abstract void ShowAndHandleInput();
}

public class LoginForm : InputForm
{
    public override void ShowAndHandleInput()
    {
        Console.WriteLine($"=== {Title} ===");
        Console.Write("请输入登录名: ");
        string login = Console.ReadLine();
        Console.Write("请输入密码: ");
        string password = Console.ReadLine();
        Console.WriteLine("认证完成!");
    }
}

public class RegistrationForm : InputForm
{
    public override void ShowAndHandleInput()
    {
        Console.WriteLine($"=== {Title} ===");
        Console.Write("请设置登录名: ");
        string login = Console.ReadLine();
        Console.Write("请设置密码: ");
        string password = Console.ReadLine();
        Console.WriteLine("注册完成!");
    }
}

用法:


InputForm[] forms = new InputForm[]
{
    new LoginForm { Title = "登录" },
    new RegistrationForm { Title = "注册" }
};

foreach (var form in forms)
{
    form.ShowAndHandleInput();
}

这个套路在你程序越写越大时会越来越有用。抽象类是搭建可扩展系统的好基石,能让你做到DRY和SOLID(不重复自己,写模块化、易扩展的代码)这些好习惯。

10. 用抽象类和方法时常见的坑

错误1:忘了实现抽象方法。
如果子类没实现所有抽象方法和属性,编译器会报错。这很常见,尤其是用别人的代码或者继承链很长的时候。一定要仔细检查,别漏了啥重要的。

错误2:想把方法同时声明成abstractstatic
这样不行。抽象方法是给实例用的,静态方法根本不需要实例。这俩思路冲突,编译器不会让你这么写。

错误3:“偷懒”不实现所有抽象成员。
你要是不想实现所有抽象方法,那你这个类也得声明成abstract。这不算错,但可能说明你的架构有点绕。可以考虑把部分实现放到中间的抽象类里,让后面的子类更轻松。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION