小白学习C#笔记 day5 ⭐以下内容可能有误请仔细甄别1.抽象类与抽象方法接day4一、为什么需要抽象类之前的 Character 父类在真实项目里根本不会被 new 出来——游戏里不会存在一个“通用角色”的单位上场的都是战士、法师这些具体职业。父类的作用只是定规矩、统一接口。这时候就可以把父类标记成抽象类• 它只能当基类被继承不能实例化不能 new Character()• 可以强制要求所有子类必须实现某个方法不写就编译报错从语法上杜绝遗漏二、核心语法1. 抽象类在 class 前加 abstract禁止实例化2. 抽象方法在方法前加 abstract没有方法体连大括号都没有子类必须用 override 重写三、完整示例沿用角色体系四、硬性规则•抽象类不能实例化new Character() 会直接报错•只要类里有一个抽象方法这个类就必须声明为抽象类• 子类继承抽象类必须重写所有抽象方法除非子类自己也是抽象类•抽象方法天生支持重写不用再加 virtual五、虚方法 vs 抽象方法 核心区别• virtual 虚方法有默认实现子类可选重写适合基类有默认逻辑、子类可改可不改的场景• abstract 抽象方法没有实现子类必须重写适合基类只定规则、所有子类都必须有自己专属实现的场景那为什么要创建出abstract这个子类必须重写的功能1.强制统一规范防止开发漏写核心功能就是怕自己漏写所以规定不能这么写我们设定所有游戏角色必须拥有释放技能的能力。如果只用普通virtual虚方法父类写默认空攻击子类程序员偷懒完全可以不重写CastSkill。最后创建刺客角色时刺客没有专属技能只会打出通用基础攻击游戏逻辑直接出bug编译器不会提醒你。换成abstract抽象方法编译器强制约束只要你写了继承这个抽象角色类不重写技能方法代码直接爆红报错根本运行不了。等于编译器帮你检查每一个新职业都必须自定义技能杜绝偷懒漏写。2.父类本身不存在完整逻辑不需要默认实现抽象父类Character只是一个概念现实里不存在“无职业的空白角色”它的CastSkill没有通用逻辑可以写。• virtual需要写一套默认代码很多时候这套默认代码毫无意义只是为了语法凑数• abstract直接省去无用的默认代码只定下“所有角色都要有技能”这条规则代码更干净表意清晰。举个项目场景对比场景做游戏道具系统所有道具都要有「使用Use()」功能1. 武器使用装备到身上2. 药水使用恢复血量3. 材料使用合成装备如果父类Item用virtual新人创建新道具子类不写Use方法直接报错立刻知道自己漏了核心逻辑提前规避bug。看下面2.总结以上抽象方法强制重写的设计本质是利用编译器做代码校验保证所有子类都完整实现父类规定的核心功能避免后期出现逻辑漏洞。2.接上一点两个父类都用的use可是里面都是空的有什么用父类里空的Use方法直接调用确实毫无用处。它存在的意义根本不是“执行代码”而是定规矩、做统一入口支撑多态。我用你最熟的游戏背包场景给你讲透核心作用让所有道具能被「统一调用」假设你做游戏背包背包里有武器、药水、材料几十种道具。玩家点“使用”按钮时总不能写几十种判断有了父类定义的Use()方法你就可以这么写• 点长剑执行的是长剑类重写的「装备」逻辑• 点红药水执行的是药水类重写的「回血」逻辑• 点铁矿石执行的是材料类重写的「合成」逻辑这就是多态父类只负责定义「所有道具都能被使用」这个规则对外暴露统一的调用入口具体怎么用由子类自己实现。上层代码背包系统完全不用关心子类细节加新道具也不用改背包代码。那空的 virtual 方法和 abstract 方法有啥区别1. 空 virtual 方法可选重写它相当于给了一个“默认空行为”适合「这个功能不是所有子类都必须有」的场景。比如道具加一个丢弃()方法大部分道具直接销毁就行父类写好默认逻辑只有少数特殊道具比如绑定装备不能丢才需要重写。缺点就是你说的如果子类忘了重写调用就会执行空逻辑悄悄出bug。2. abstract 抽象方法强制重写它连空方法体都不给直接就是一条规则「只要你继承我就必须实现这个功能」。像Use()这种核心功能所有道具都必须有就适合用abstract——不重写直接编译报错从根源杜绝漏写。3.接口interface•抽象类解决「是什么」的问题是从属关系。战士「是」角色怪物「是」角色本质同类所以继承同一个父类。•接口解决「能做什么」的问题是能力契约。战士「能攻击」、宝箱「能被打开」、传送门「能交互」——这些是不同类都可以具备的能力和它本质是什么东西没关系。接口就是一份能力约定书只规定“必须有这个方法”不写具体怎么实现哪个类想拥有这个能力就去实现这个接口自己把逻辑写出来。一、核心语法规则1. 命名规范习惯以大写 I 开头后面跟能力描述比如 IAttackable、IMovable、IInteractable2. 成员限制内部只能写方法没有方法体、属性、事件不能写字段、不能有构造函数、不能写普通方法的实现3. 默认修饰符所有成员默认就是 public abstract不用手动写写了反而多余4. 实现语法类实现接口同样用冒号 :如果同时继承类接口类写前面接口写后面5. 突破单继承C# 中类只能继承1个父类但可以实现无数个接口这是接口最核心的价值二、代码实战基于角色体系扩展接口我们在你之前写的角色抽象类基础上叠加两个能力接口感受一下“单继承多实现”的组合用法。第一步定义两个接口接口里只有方法签名没有大括号里的实现比抽象方法更“纯粹”只定规矩、不写逻辑。第二步子类实现多个接口战士是角色同时具备攻击和移动能力所以继承父类 实现两个接口三、接口的真正威力面向接口编程光看上面的代码你可能觉得“和写个普通方法也没区别”。接口的核心价值是用契约解耦实现多态。比如我们写一个通用的战斗触发方法为什么不写interface-看4.调用的时候只要实现了 IAttackable 接口不管是什么类都能传进去这就是面向接口的好处• 方法不依赖具体的战士、箭塔这些类只依赖「能攻击」这个能力• 以后加新的攻击单位比如召唤物、炮台、陷阱不用改 TriggerAttack 一行代码只要新类实现 IAttackable 就行• 完全符合「对扩展开放对修改关闭」的设计原则和抽象对比• 几个类本质相同、有共同状态、需要复用大量逻辑用抽象类做基类比如角色基类、怪物基类• 几个类本质不同、只是恰好有同一项能力用接口扩展比如可攻击、可交互、可销毁• 实际游戏开发里两者永远搭配用基类抽通用逻辑接口叠加扩展能力4.上一题的通用战斗触发方法为什么里面没有定义interface而是直接用的TriggerAttack类比你已经会的知识一秒想通你早就会用「父类当参数」实现多态比如• 定义类必须写 class 关键字• 把类当类型用变量、参数、返回值直接写类名不用加 class接口的规则和类完全一模一样interface 只负责告诉编译器「这是在定义一个接口」定义完成后IAttackable 就是一个完整的类型名了直接用就行。总结interface 是定义时的关键字IAttackable 是定义好的类型名就像 class 是定义类的关键字Character 是类名一样用的时候只写类型名不写定义关键字。