在软件开闭原则实现中,使用抽象类而非普通类的必要性是什么?
嘿,你的疑问其实很常见——乍一看好像普通类也能搞定继承,但抽象类在开闭原则的语境下,其实是给你加了好几层“安全防护”和“设计约束”,咱们结合你贴的代码一步步说:
首先,先纠正一个你可能没注意到的误区:如果把Vehicle改成普通类,你原来的代码逻辑其实跑不起来(或者说不符合你的预期)。咱们拆解看:
你原来的代码里是Vehicle v = new Truck(); v.Run();——如果Vehicle是普通类,那要么:
Vehicle里没有Run方法:那v.Run()直接编译报错,因为父类没有定义这个方法,父类引用没法调用子类独有的方法;Vehicle里给Run加个默认实现:比如public void Run() { Console.WriteLine("默认运行"); },那Truck如果不重写Run,就会继承这个默认逻辑,这时候你调用v.Run()会输出默认内容,显然不符合“卡车有自己的运行逻辑”的需求,而且很容易因为忘记重写导致隐性bug。
这时候抽象类的第一个优势就体现出来了:强制子类必须实现抽象方法,编译期就拦截错误。
当Vehicle是抽象类,Run是抽象方法时,任何继承它的普通子类(比如Truck)都必须实现Run,否则直接编译失败——这相当于给所有子类定了个“契约”:所有车辆必须有自己的Run逻辑,绝不能偷懒用默认实现。这完美契合开闭原则:你要扩展新的车型(比如Motorcycle),只需要新增一个子类并实现Run就行,完全不用修改Vehicle里的Stop和Fill(对修改关闭),同时支持无限扩展新车型(对扩展开放)。
第二个优势:禁止实例化抽象类,从设计层面避免不合理的对象创建。
“Vehicle”本身是个抽象概念,你不可能造出一个“既不是汽车也不是卡车的通用车辆”对吧?如果Vehicle是普通类,有人可能不小心写出new Vehicle(),然后调用它的方法——这在逻辑上是不合理的。抽象类从语法上就禁止了这种操作,保证所有实例都是具体的子类对象,让代码的语义更清晰,也避免了因为误用父类实例导致的逻辑错误。
第三个优势:明确区分“通用逻辑”和“必须定制的逻辑”。
在你的代码里,Stop和Fill是所有车辆都通用的逻辑,所以放在抽象类里实现好;而Run是每个车辆都不一样的,所以定义成抽象方法,强制子类定制。这种划分非常清晰:通用逻辑统一维护(不用改),定制逻辑交给子类扩展(随便加),完全贴合开闭原则的核心思想——对扩展开放,对修改关闭。
如果用普通类,你要么把Run写成虚方法(virtual),但虚方法不强制子类重写,很容易遗漏;要么把Run的默认实现写得很通用,但后续如果要改默认逻辑,就会影响所有没重写的子类,违反了“对修改关闭”的原则。
最后再回到你的代码:你原来的Truck类没有实现Run,其实这是编译不过的——因为继承抽象类的普通子类必须实现所有抽象方法。这就是抽象类的“强制约束”在起作用,它确保你不会写出逻辑不完整的子类。
备注:内容来源于stack exchange,提问作者bokabokaboka

