面试题答案
一键面试1. 设计哲学
- 抽象类:抽象类可以包含抽象方法和具体方法。抽象方法只有声明没有实现,具体实现由子类完成。它主要用于定义一系列相关类的通用行为和属性,体现了一种“is - a”的关系,是对具体事物的抽象。
- 接口:接口只定义方法签名和属性,不包含任何实现。它侧重于定义对象的形状,强调的是对象应该具备哪些行为,体现了一种“like - a”的关系。
2. 提升可维护性和扩展性的方式
- 可维护性:通过将通用的逻辑抽象到抽象类中,具体子类继承抽象类并实现抽象方法,这样在修改通用逻辑时,只需要修改抽象类中的代码,所有子类都会自动应用这些修改。接口定义了对象的契约,使得代码的依赖关系更加清晰,当接口的实现发生变化时,只要接口不变,依赖该接口的代码就无需修改。
- 扩展性:当有新的需求时,可以通过创建新的子类继承抽象类,实现其抽象方法来扩展功能。同时,实现接口的新类也可以轻松地融入到现有的系统中,只要满足接口定义的契约即可。
3. 代码示例
假设我们正在开发一个图形绘制的企业级项目,有不同类型的图形,如圆形、矩形等。
// 定义一个形状接口
interface Shape {
calculateArea(): number;
}
// 定义一个抽象的图形类,包含一些通用属性和方法
abstract class Graphic {
protected color: string;
constructor(color: string) {
this.color = color;
}
// 抽象方法,由具体子类实现
abstract draw(): void;
// 具体方法,可被子类复用
setColor(newColor: string): void {
this.color = newColor;
}
}
// 圆形类,继承Graphic类并实现Shape接口
class Circle extends Graphic implements Shape {
private radius: number;
constructor(color: string, radius: number) {
super(color);
this.radius = radius;
}
calculateArea(): number {
return Math.PI * this.radius * this.radius;
}
draw(): void {
console.log(`Drawing a ${this.color} circle with radius ${this.radius}`);
}
}
// 矩形类,继承Graphic类并实现Shape接口
class Rectangle extends Graphic implements Shape {
private width: number;
private height: number;
constructor(color: string, width: number, height: number) {
super(color);
this.width = width;
this.height = height;
}
calculateArea(): number {
return this.width * this.height;
}
draw(): void {
console.log(`Drawing a ${this.color} rectangle with width ${this.width} and height ${this.height}`);
}
}
// 使用示例
const circle = new Circle('red', 5);
circle.draw();
console.log(`Circle area: ${circle.calculateArea()}`);
const rectangle = new Rectangle('blue', 4, 6);
rectangle.draw();
console.log(`Rectangle area: ${rectangle.calculateArea()}`);
4. 架构设计思路
- 分层架构:在大型项目中,可以将业务逻辑按照功能划分为不同的层次,如数据访问层、业务逻辑层、表示层等。在每一层中,使用抽象类和接口来定义规范和通用行为。例如,在数据访问层,定义一个抽象的数据访问类,包含获取数据、保存数据等抽象方法,具体的数据访问实现类(如数据库访问类、文件访问类等)继承该抽象类并实现相应方法。在业务逻辑层,使用接口定义业务规则,不同的业务实现类实现这些接口。
- 依赖注入:通过依赖注入的方式,将依赖的对象通过构造函数或方法参数传递进来。这样可以提高代码的可测试性和可维护性,同时便于在运行时替换不同的实现。例如,在一个服务类中,通过构造函数注入实现了某个接口的对象,这样在测试时可以传入模拟对象,而在实际运行时传入真实的实现对象。
- 模块划分:将项目划分为多个模块,每个模块负责特定的功能。模块之间通过接口进行通信,模块内部可以使用抽象类来封装通用逻辑。这样可以降低模块之间的耦合度,提高代码的可维护性和扩展性。当需要对某个模块进行修改或扩展时,不会影响到其他模块。