面向对象设计原则是一组用于指导良好的软件设计的基本准则。这些原则帮助开发人员创建可维护、可扩展和易于理解的代码,所有的设计模式都会遵循这些基本原则。
1、单一职责原则
单一职责原则强调一个类只有一个单一职责,将一个类的功能限制在一个明确定义的领域内。
// 不遵循单一职责原则的示例
class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public void saveToDatabase() {
// 将员工信息保存到数据库
// ...
}
public void logTransaction() {
// 记录员工信息保存操作的日志
// ...
}
}
上面Employee类既负责将员工数据保存到数据库(saveToDatabase方法),又负责保存操作日志(logTransaction),它违反了单一职责原则,因为它有两个不同的职责:数据持久化和日志记录,日志记录是应用程序常见任务,它可被视为一个独立的职责,应该分离出来。重构后代码如下:
// 遵循单一职责原则的示例
class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
}
class EmployeeRepository {
public void saveToDatabase(Employee employee) {
// 将员工信息保存到数据库
// ...
}
}
class Logger {
public void logTransaction(String message) {
// 记录日志
// ...
}
}
2、开闭原则
开闭原则它强调软件实体应该对扩展开放,对修改关闭,就是当要添加新功能时,应该通过扩展现有代码,而不是修改现有代码。
// 不遵循开闭原则的示例
class Rectangle {
double width;
double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
}
class Circle {
double radius;
public Circle(double radius) {
this.radius = radius;
}
}
class AreaCalculator {
public double calculateRectangleArea(Rectangle rectangle) {
return rectangle.width * rectangle.height;
}
public double calculateCircleArea(Circle circle) {
return Math.PI * circle.radius * circle.radius;
}
}
在上述设计中,AreaCalculator
类负责计算矩形和圆形的面积。然而,如果我们想要添加更多的图形类型,例如三角形、椭圆等,就需要不断修改 AreaCalculator
类,违反了开闭原则。
现在,让我们使用开闭原则来改进这个设计。我们可以引入一个抽象图形类,并为每种具体的图形创建子类。然后,我们可以使用多态性来计算图形的面积,而不需要修改 AreaCalculator
类。
// 遵循开闭原则的示例
abstract class Shape {
abstract double calculateArea();
}
class Rectangle extends Shape {
double width;
double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
double calculateArea() {
return width * height;
}
}
class Circle extends Shape {
double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
double calculateArea() {
return Math.PI * radius * radius;
}
}
class AreaCalculator {
public double calculateArea(Shape shape) {
return shape.calculateArea();
}
}
通过这种改进,我们可以轻松地添加新的图形类型,只需创建新的图形子类并实现 calculateArea
方法,而不需要修改现有代码。这遵循了开闭原则,使得程序更加灵活和可扩展。
3、里氏替换原则
如果一个类型是另一个类型的子类,那么它应该可以替代父类型而不引起错误,但是反过来则不成立。
class Bird {
void fly() {
System.out.println("Bird can fly");
}
}
class Sparrow extends Bird {
// Sparrow类继承了Bird类,并且保持了相同的行为
}
class Ostrich extends Bird {
@Override
void fly() {
System.out.println("Ostrich can't fly"); // 重写了fly方法,但仍然遵循Bird类的行为
}
}
public class LSPExample {
public static void main(String[] args) {
Bird bird1 = new Sparrow();
Bird bird2 = new Ostrich();
bird1.fly(); // 输出:Bird can fly
bird2.fly(); // 输出:Ostrich can't fly
}
}
在这个示例中,Bird
是基类,而 Sparrow
和 Ostrich
是继承自 Bird
的子类。虽然 Ostrich
重写了 fly
方法,但它仍然遵循了 Bird
类的行为,即打印出一条关于飞行能力的消息。这个示例展示了里氏替换原则的应用,即子类可以替代父类而不破坏程序的正确性,同时可以在子类中扩展或修改行为。这提高了代码的灵活性和可扩展性。
4、依赖倒转原则
依赖倒转原则强调高层模块不应该依赖于低层模块,它们都应该依赖于抽象。同时,抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
// 不符合依赖倒转原则的示例
class Switch {
void turnOn() {
// 打开电子设备
}
void turnOff() {
// 关闭电子设备
}
}
上面例子Switch
类用于控制电子设备的开关,高层模块依赖于具体的Switch类,这违反了依赖倒转原则。
// 使用依赖倒转原则来
interface Device {
void turnOn();
void turnOff();
}
然后,我们修改 Switch
类,使其依赖于抽象的 Device
接口:
class Switch {
private Device device;
public Switch(Device device) {
this.device = device;
}
void turnOn() {
device.turnOn();
}
void turnOff() {
device.turnOff();
}
}
现在,Switch
类不再直接控制具体的电子设备,而是通过 Device
接口来控制.
这种改进遵循了依赖倒转原则,如果需要添加新的电子设备,只需创建一个实现 Device
接口的新类,并将其传递给 Switch
对象即可,而不需要修改现有代码。
5、接口隔离原则
接口隔离原则强调客户不应该它们不需要的接口,一个类对其它的类有最小的依赖。
// 不遵循接口隔离原则示例
interface MediaPlayer {
void playAudio();
void playVideo();
}
class AudioPlayer implements MediaPlayer {
@Override
public void playAudio() {
System.out.println("Playing audio...");
}
@Override
public void playVideo() {
// 实际上不需要实现这个方法
}
}
class VideoPlayer implements MediaPlayer {
@Override
public void playAudio() {
// 实际上不需要实现这个方法
}
@Override
public void playVideo() {
System.out.println("Playing video...");
}
}
这个设计违反了接口隔离原则,因为每个具体类都不需要实现的方法都被强制实现了。例如,AudioPlayer
不需要实现 playVideo
方法,而 VideoPlayer
不需要实现 playAudio
方法。
// 遵循接口隔离原则示例
采用更小的接口,分别用于音频播放和视频播放:
interface AudioPlayer {
void playAudio();
}
interface VideoPlayer {
void playVideo();
}
class BasicAudioPlayer implements AudioPlayer {
@Override
public void playAudio() {
System.out.println("Playing audio...");
}
}
class BasicVideoPlayer implements VideoPlayer {
@Override
public void playVideo() {
System.out.println("Playing video...");
}
}
改进后的设计中,我们定义了两个更小的接口:AudioPlayer
和 VideoPlayer
,分别用于音频播放和视频播放。每个类只需要实现其自身所需的接口,不再需要强制性地实现不需要的方法。
6、合成复用原则
合成复用原则强调在软件设计中应该尽量使用合成(组合)而不是继承来实现代码的复用。
// 不遵循合成复用原则示例
class Shape {
void draw() {
// 绘制图形的通用代码
}
}
class Rectangle extends Shape {
@Override
void draw() {
// 绘制矩形的具体代码
}
}
class Circle extends Shape {
@Override
void draw() {
// 绘制圆形的具体代码
}
}
在这个设计中,我们使用继承来实现不同类型的图形,它违反了合成复用原则。如果以后需要添加新的图形类型,我们可能会不断创建新的子类,这会导致类层次结构的膨胀和复杂性的增加。
// 遵循合成复用原则示例
interface Drawable {
void draw();
}
class Rectangle implements Drawable {
@Override
public void draw() {
// 绘制矩形的具体代码
}
}
class Circle implements Drawable {
@Override
public void draw() {
// 绘制圆形的具体代码
}
}
class DrawingBoard {
private List<Drawable> shapes = new ArrayList<>();
public void addShape(Drawable shape) {
shapes.add(shape);
}
public void render() {
for (Drawable shape : shapes) {
shape.draw();
}
}
}
改进后的设计中,我们定义了一个 Drawable
接口,表示可以绘制的对象。每个图形类实现了这个接口。然后,我们创建了一个 DrawingBoard
类,它负责管理要绘制的图形对象。通过合成复用,我们可以轻松地添加新的图形类型,只需实现 Drawable
接口并将其添加到 DrawingBoard
中即可。
7、迪米特法则
迪米特法则强调了模块之间应该保持松散的耦合,一个对象应该尽可能少地了解其他对象的内部结构。
// 不遵循迪米特法则示例
class TVRemote {
private TV tv;
public TVRemote(TV tv) {
this.tv = tv;
}
public void turnOn() {
tv.turnOn();
}
public void turnOff() {
tv.turnOff();
}
}
class TV {
public void turnOn() {
System.out.println("TV is turned on.");
}
public void turnOff() {
System.out.println("TV is turned off.");
}
}
上面代码是一个电视遥控器设计,在这个设计中,TVRemote
类需要了解 TV
类的内部细节,它直接与 TV
类交互来控制电视的开关。这种设计违反了迪米特法则。
// 遵循迪米特法则示例
class TVRemote {
public void turnOn(TV tv) {
tv.turnOn();
}
public void turnOff(TV tv) {
tv.turnOff();
}
}
class TV {
public void turnOn() {
System.out.println("TV is turned on.");
}
public void turnOff() {
System.out.println("TV is turned off.");
}
}
改进后的设计中,TVRemote
类不再持有 TV
对象,而是通过参数传递 TV
对象来进行操作。这降低了对象之间的依赖关系,使得代码更加松散耦合,符合迪米特法则的要求。