【魔法森林冒险】2/14 抽象层设计:Figure/Person类(所有角色的基石)

🏠个人主页:黎雁

🎬作者简介:C/C++/JAVA后端开发学习者

❄️个人专栏:C语言数据结构(C语言)EasyXJAVA游戏规划程序人生

✨ 从来绝巘须孤往,万里同尘即玉京

文章目录

  • [【魔法森林冒险】2/14 抽象层设计:Figure/Person类(所有角色的基石)🏗️](#【魔法森林冒险】2/14 抽象层设计:Figure/Person类(所有角色的基石)🏗️)
    • [📝 文章摘要](#📝 文章摘要)
    • [🧱 一、为什么需要抽象层?------ 从「重复代码」到「优雅复用」](#🧱 一、为什么需要抽象层?—— 从「重复代码」到「优雅复用」)
    • [📜 二、核心代码拆解:Figure类(顶级角色抽象类)](#📜 二、核心代码拆解:Figure类(顶级角色抽象类))
      • [2.1 完整代码(核心片段)](#2.1 完整代码(核心片段))
      • [2.2 关键知识点讲解💡](#2.2 关键知识点讲解💡)
    • [👤 三、核心代码拆解:Person类(可交互角色抽象类)](#👤 三、核心代码拆解:Person类(可交互角色抽象类))
      • [3.1 完整代码(核心片段)](#3.1 完整代码(核心片段))
      • [3.2 关键知识点讲解💡](#3.2 关键知识点讲解💡)
    • [🚫 四、新手使用抽象类的3个高频易错点](#🚫 四、新手使用抽象类的3个高频易错点)
    • [🌐 五、抽象层如何支撑所有角色的拓展?](#🌐 五、抽象层如何支撑所有角色的拓展?)
    • [📌 知识回顾](#📌 知识回顾)
    • [✍️ 写在最后](#✍️ 写在最后)

【魔法森林冒险】2/14 抽象层设计:Figure/Person类(所有角色的基石)🏗️

📝 文章摘要

内容维度 详情说明
核心摘要 本文是「魔法森林冒险」Java项目系列第二篇,聚焦游戏的「角色抽象层」核心------Figure和Person类。带你吃透抽象类的设计逻辑:为什么要设计这两个顶层抽象类?通用属性/方法如何封装?Allen/Lia/老贤者等角色如何基于此扩展?同时拆解新手使用抽象类的高频坑点。
阅读时长 10分钟
适合人群 1. Java新手:想理解抽象类的实际应用场景;2. 面向对象学习者:想掌握「代码复用+拓展性」的设计思路;3. 项目复刻者:想搞懂角色体系的底层逻辑。
阅读重点 1. Figure/Person类的核心属性与方法设计;2. 抽象方法的预留扩展逻辑;3. 新手使用抽象类的3个易错点;4. 抽象类如何支撑Allen/Lia等角色的拓展。

🧱 一、为什么需要抽象层?------ 从「重复代码」到「优雅复用」

在开发游戏角色时,你会发现:Allen、Lia、老贤者、甚至敌人,都有共同的属性/行为(比如都有生命值、都能判断是否存活、都有名称)。

如果为每个角色单独写这些逻辑,会出现大量重复代码❌:

  • Allen写一遍isAlive()(判断存活);
  • Lia再写一遍isAlive()
  • 老贤者还要写一遍...

而抽象类(abstract class)就是解决这个问题的「利器」✅:

抽象类 = 通用属性/方法(实现) + 抽象方法(只定义,子类实现)

作用:为子类提供「模板」,既复用通用逻辑,又强制子类实现专属功能。

本项目中,Figure是「所有角色的顶级抽象模板」,Person是「可交互角色的专属模板」,二者共同构成了所有角色的底层基石。

📜 二、核心代码拆解:Figure类(顶级角色抽象类)

先看你提供的Figure.java核心代码(未修改,仅拆分讲解):

2.1 完整代码(核心片段)

java 复制代码
/**
 * 所有角色的顶级抽象类
 * 定义所有角色的通用状态和基础行为
 */
public abstract class Figure {
    // 通用属性:角色名称
    private String name;
    // 通用属性:是否存活(所有角色都有这个状态)
    private boolean isAlive;
    // 通用属性:角色描述
    private String description;

    // 构造方法:初始化通用属性
    public Figure(String name, String description) {
        this.name = name;
        this.description = description;
        this.isAlive = true; // 初始默认存活
    }

    // 通用方法:判断是否存活(所有角色通用,直接实现)
    public boolean isAlive() {
        return this.isAlive;
    }

    // 通用方法:设置存活状态(比如战斗死亡后调用)
    public void setAlive(boolean alive) {
        isAlive = alive;
    }

    // 抽象方法:角色的专属行为(强制子类实现)
    // 比如Allen的行为是「冒险」,Lia的行为是「辅助」,敌人的行为是「攻击」
    public abstract void act();

    // Getter/Setter(封装属性,新手必学)
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

2.2 关键知识点讲解💡

  1. 通用属性设计

    • name/description:所有角色都有「名字」和「描述」,无需子类重复定义;
    • isAlive:所有角色都有「存活状态」,初始值true(出生即存活)。
  2. 通用方法设计

    • isAlive()/setAlive():判断/修改存活状态,所有角色逻辑完全一致,直接实现;
    • 这就是「代码复用」------ 子类继承Figure后,直接能用这些方法,不用自己写。
  3. 抽象方法act()

    • abstract修饰,只有方法签名,没有方法体;
    • 作用:强制所有子类必须实现「角色专属行为」(比如Allen的act()是选择冒险行动,Goblin的act()是选择攻击/逃跑);
    • 新手坑:如果子类不实现抽象方法,编译直接报错!

👤 三、核心代码拆解:Person类(可交互角色抽象类)

Person继承自Figure,是「玩家/盟友/NPC」的专属抽象类(敌人不继承Person,因为敌人的交互逻辑不同)。

3.1 完整代码(核心片段)

java 复制代码
/**
 * 可交互角色抽象类(玩家/盟友/老贤者等)
 * 继承Figure,扩展可交互角色的专属属性/方法
 */
public abstract class Person extends Figure {
    // 专属属性:生命值(HP)
    private int hp;
    // 专属属性:魔法值(MP)
    private int mp;
    // 专属属性:金币(用于交易)
    private int gold;
    // 专属属性:背包(存储道具,用数组实现)
    private Item[] backpack;
    // 背包容量
    private static final int BACKPACK_CAPACITY = 10;

    // 构造方法:先调用父类Figure的构造,再初始化专属属性
    public Person(String name, String description, int hp, int mp, int gold) {
        super(name, description); // 必须先调用父类构造
        this.hp = hp;
        this.mp = mp;
        this.gold = gold;
        this.backpack = new Item[BACKPACK_CAPACITY]; // 初始化背包数组
    }

    // 通用方法:获取HP(封装,避免直接修改)
    public int getHp() {
        return hp;
    }

    // 通用方法:设置HP(带校验,避免HP为负)
    public void setHp(int hp) {
        if (hp < 0) {
            this.hp = 0; // HP最低为0,不能为负
            this.setAlive(false); // HP为0则死亡
        } else {
            this.hp = hp;
        }
    }

    // 通用方法:获取MP
    public int getMp() {
        return mp;
    }

    // 通用方法:设置MP(带校验)
    public void setMp(int mp) {
        this.mp = Math.max(mp, 0); // 简化校验:MP最低为0
    }

    // 通用方法:拾取道具(背包逻辑,所有可交互角色通用)
    public boolean pickItem(Item item) {
        // 遍历背包,找空位置
        for (int i = 0; i < backpack.length; i++) {
            if (backpack[i] == null) {
                backpack[i] = item;
                System.out.println(this.getName() + "拾取了:" + item.getName());
                return true;
            }
        }
        // 背包满了
        System.out.println(this.getName() + "的背包已满,无法拾取" + item.getName());
        return false;
    }

    // 抽象方法:使用道具(不同角色使用道具逻辑不同,强制子类实现)
    public abstract boolean useItem(Item item);

    // 抽象方法:交易(Allen和老贤者交易,Lia不交易,逻辑不同)
    public abstract boolean trade(Person target, Item item, int gold);

    // 其他Getter/Setter省略(和Figure类逻辑一致)
}

3.2 关键知识点讲解💡

  1. 继承的核心用法

    • extends Figure:Person继承Figure的所有非私有属性/方法(name/isAlive()等);
    • super(name, description):构造方法中必须先调用父类构造,这是Java继承的强制规则。
  2. 专属属性设计

    • hp/mp/gold:玩家/盟友/NPC的核心资源,敌人虽然也有HP,但因为交互逻辑不同,敌人继承Figure而非Person;
    • backpack:用数组实现背包(容量10),所有可交互角色都有背包,复用这个逻辑。
  3. 属性封装(重点)

    • 所有属性用private修饰,通过getter/setter访问;
    • setHp()带校验:HP不能为负,HP为0时自动设置为死亡(setAlive(false));
    • 新手坑:直接暴露属性(比如public int hp)会导致数据混乱,封装是面向对象的核心!
  4. 抽象方法的拓展性

    • useItem():Allen使用治疗药水是恢复HP,Lia使用魔法花是提升信任度,逻辑不同,所以定义为抽象方法;
    • trade():Allen可以和老贤者交易,Lia不能交易,强制子类按自身逻辑实现。

🚫 四、新手使用抽象类的3个高频易错点

结合本项目,总结新手最容易踩的坑:

易错点1:试图实例化抽象类

java 复制代码
// 错误示例:抽象类不能new对象!
Figure figure = new Figure("测试角色", "测试描述"); 
Person person = new Person("Allen", "魔法学徒", 100, 50, 10);

✅ 正确做法:只能实例化「继承抽象类的子类」(比如Allen、Lia):

java 复制代码
Allen allen = new Allen("艾伦", "魔法学徒", 100, 50, 10);

易错点2:子类未实现所有抽象方法

如果Allen类只实现了act(),没实现useItem(),编译报错:

Error: Allen is not abstract and does not override abstract method useItem(Item) in Person

✅ 正确做法:子类必须实现父类所有抽象方法(除非子类也是抽象类)。

易错点3:忽略构造方法的调用顺序

如果Person的构造方法没写super(name, description),编译报错:

Error: Constructor call must be the first statement in a constructor

✅ 正确做法:子类构造方法第一行必须调用父类构造(super()),如果父类无参构造,可省略;如果父类有参构造,必须显式调用。

🌐 五、抽象层如何支撑所有角色的拓展?

用一张图看懂Figure/Person的「承上启下」作用:
Figure(顶级抽象)
Person(可交互角色抽象)
Allen(玩家,实现act/useItem/trade)
Lia(盟友,实现act/useItem/trade)
OldSage(老贤者,实现act/useItem/trade)
Enemy(敌人抽象,实现act)
Goblin(哥布林)
TreeSpirit(树灵BOSS)
Natural(中立角色,实现act)
Animal(受伤动物)

核心逻辑:

  1. 向上复用:Allen/Lia/老贤者都复用Person的「HP/MP/背包/拾取道具」逻辑;
  2. 向下拓展 :每个角色通过实现抽象方法,拥有专属行为(比如Allen的useItem()是用治疗药水,Lia的useItem()是用魔法花);
  3. 横向隔离:敌人(Enemy)和可交互角色(Person)都继承Figure,但各自拓展不同属性,逻辑不混淆。

📌 知识回顾

  1. Figure是所有角色的顶级抽象类,定义「存活状态/名称」等通用属性/方法,抽象方法act()强制子类实现专属行为;
  2. Person继承Figure,是可交互角色的抽象类,扩展「HP/MP/金币/背包」等专属属性,抽象方法useItem()/trade()支撑角色差异化;
  3. 抽象类的核心价值是「复用通用逻辑+强制子类实现专属逻辑」,避免重复代码;
  4. 新手使用抽象类的3个坑:实例化抽象类、未实现所有抽象方法、构造方法调用顺序错误。

✍️ 写在最后

Figure和Person类看似简单,却是整个项目「面向对象设计」的灵魂------正是因为有了这两层抽象,后续新增角色(比如矮人、精灵盟友)时,只需继承对应抽象类,实现专属方法,无需重复写通用逻辑。

下一篇我们会聚焦「Allen类(一):主角核心属性与初始化」,带你看作为玩家的Allen,如何基于Person抽象类,实现专属的属性设计和初始化逻辑💪。

如果你是新手,建议:

  1. 把Figure/Person的代码复制到IDE中,尝试写一个「测试子类」(比如TestPerson extends Person),实现所有抽象方法,理解继承和抽象的用法;
  2. 思考:Enemy类继承Figure后,应该扩展哪些专属属性?(比如攻击力、防御力)。

🔥 系列文章导航:

  1. 项目总览:设计与架构
  2. 抽象层设计:Figure/Person类(本文)
  3. Allen类(一):核心属性与初始化
    ...(后续篇章持续更新)
    💬 评论区互动:你觉得抽象类和普通类最大的区别是什么?在这个游戏项目中,还有哪些地方可以用抽象类优化?
相关推荐
季明洵2 小时前
C语言实现单链表
c语言·开发语言·数据结构·算法·链表
墨雪不会编程2 小时前
C++之【深入理解Vector】三部曲最终章
开发语言·c++
怒放吧德德2 小时前
后端 Mock 实战:Spring Boot 3 实现入站 & 出站接口模拟
java·后端·设计
浅念-2 小时前
C语言编译与链接全流程:从源码到可执行程序的幕后之旅
c语言·开发语言·数据结构·经验分享·笔记·学习·算法
biyezuopinvip2 小时前
基于Spring Boot的企业网盘的设计与实现(任务书)
java·spring boot·后端·vue·ssm·任务书·企业网盘的设计与实现
脸大是真的好~2 小时前
EasyExcel的使用
java·excel
小宋10212 小时前
Java 项目结构 vs Python 项目结构:如何快速搭一个可跑项目
java·开发语言·python
JavaGuide3 小时前
一款悄然崛起的国产规则引擎,让业务编排效率提升 10 倍!
java·spring boot