如何通过Java代码实践体现设计原则面试高频题的应用?
摘要:设计原则面试高频题+Java代码实践(吃透面试+落地开发) 结合面试高频场景,我会先整理核心面试题+标准答案(覆盖90%面试考点),再用Java代码逐一演示七大设计原则的落地用法,兼顾“
设计原则面试高频题+Java代码实践(吃透面试+落地开发)
结合面试高频场景,我会先整理核心面试题+标准答案(覆盖90%面试考点),再用Java代码逐一演示七大设计原则的落地用法,兼顾“面试答题”和“实际开发”双重需求。
一、设计原则高频面试题+标准答案
基础必问(80%面试官会问)
1. 请说说SOLID五大设计原则分别是什么,各自的核心思想?
答案:
SOLID是面向对象设计的核心原则,拆解为5个原则:
单一职责(SRP):一个类只负责一个职责,变更原因唯一,核心是“高内聚”,避免一个类承担过多功能导致修改牵一发而动全身;
开闭原则(OCP):对扩展开放、对修改关闭,核心是依赖抽象而非具体实现,新增功能通过扩展子类/实现类完成,不修改原有稳定代码;
里氏替换(LSP):子类可无缝替换父类且程序行为不变,核心是保证继承的合理性,子类不能破坏父类的约定(如不能重写父类非抽象方法、不能缩小方法的访问权限);
接口隔离(ISP):客户端不依赖不需要的接口,核心是将臃肿接口拆分为细粒度接口,避免接口变更影响无关客户端;
依赖倒置(DIP):高层模块不依赖低层模块,二者都依赖抽象;抽象不依赖细节,细节依赖抽象,核心是“面向接口编程”,解除模块间的强耦合。
2. 开闭原则是设计原则的核心目标,如何在代码中落地开闭原则?
答案:
开闭原则的核心是“抽象化”,落地关键有3点:
定义抽象层(接口/抽象类):将稳定的业务逻辑抽象为接口,封装不变的行为;
具体实现类扩展抽象层:新增功能时,编写新的实现类继承/实现抽象层,不修改原有实现;
依赖注入解耦:高层模块通过抽象层调用功能,而非直接依赖具体实现类。
举例:支付场景中,定义PayService接口,原有AlipayPayService实现支付宝支付,新增微信支付时,只需新增WechatPayService实现PayService,无需修改原有支付逻辑。
3. 里氏替换原则的核心要求是什么?举一个违反里氏替换的例子。
答案:
核心要求:所有引用父类的地方,替换为子类后程序的行为和逻辑不变,具体约束:
子类不能重写父类的非抽象方法;
子类不能扩大方法的前置条件(如父类方法参数是Object,子类不能改为String);
子类不能缩小方法的后置条件(如父类返回List,子类不能返回ArrayList并限制元素类型);
子类不能抛出父类未声明的异常(运行时异常除外)。
反例:
// 父类:鸟类定义飞的行为
class Bird {
public void fly() {
System.out.println("鸟类飞行");
}
}
// 子类:鸵鸟,违反里氏替换
class Ostrich extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException("鸵鸟不会飞");
}
}
// 调用方:依赖父类,但替换子类后报错
public class Test {
public static void letBirdFly(Bird bird) {
bird.fly();
}
public static void main(String[] args) {
letBirdFly(new Ostrich()); // 抛出异常,违反里氏替换
}
}
修正方案:拆分抽象层,定义Flyable接口,只有会飞的鸟实现该接口,鸵鸟不实现。
4. 组合/聚合复用原则(CRP)和继承的区别?为什么优先组合?
答案:
维度
继承(is-a)
组合/聚合(has-a)
耦合程度
强耦合(父类变子类必变)
弱耦合(只需调整依赖)
复用灵活性
静态复用(编译期确定)
动态复用(运行时可替换)
代码冗余
易产生冗余(继承无关方法)
无冗余(按需依赖)
优先组合的原因:
避免继承的“侵入性”:子类会继承父类所有公开方法,即使不需要;
降低耦合:组合可在运行时替换依赖的对象,继承一旦确定无法修改;
符合开闭原则:新增功能只需替换组合的对象,无需修改原有类。
进阶扩展(面试加分题)
5. 迪米特法则(最少知识原则)如何落地?举代码例子说明。
