设计模式六大原则:面向对象设计的核心
在面向对象编程(OOP)中,设计模式为编写高效、可维护且可扩展的代码提供了重要指导。而这背后的核心是设计模式中的六大原则。本文将详细介绍这些原则,并使用Java代码实例进行说明。
1. 单一职责原则(Single Responsibility Principle, SRP)
通俗解释:
每个人只做自己分内的事,不要什么都想做。一个类只负责一个功能,不要让它承担太多职责。如果一个类既管账又做市场推广,那以后改起来会很麻烦。
原则说明:
单一职责原则要求一个类应该只有一个引起它变化的原因。换句话说,类应该专注于完成一个职责。将多个职责混合在同一个类中会导致类的复杂度增加,维护起来更加困难。
Java 代码示例:
class User {
private String name;
private String email;
public User(String name, String email) {
this.name = name;
this.email = email;
}
public void save() {
System.out.println("Saving user " + name + " to the database");
}
public void log(String message) {
System.out.println("Log: " + message);
}
}
上面代码违反了单一职责原则,因为User
类既负责用户信息的保存,又负责日志记录。为了遵守SRP,可以把日志功能分离到一个单独的Logger
类中:
class Logger {
public void log(String message) {
System.out.println("Log: " + message);
}
}
class User {
private String name;
private String email;
private Logger logger;
public User(String name, String email) {
this.name = name;
this.email = email;
this.logger = new Logger();
}
public void save() {
System.out.println("Saving user " + name + " to the database");
logger.log("User " + name + " saved successfully");
}
}
通过分离日志和用户信息保存的职责,每个类只专注于一个职责,从而提高了代码的可维护性。
2. 开闭原则(Open-Closed Principle, OCP)
通俗解释:
新的需求来了,咱们应该在不动老代码的前提下,尽量通过“扩展”来增加新功能,而不是改动现有的东西。就像装修房子的时候,尽量别砸老墙,只在新地方加点装饰。
原则说明1:
开闭原则要求软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。也就是说,添加新功能时不应该修改已有的代码,而是通过扩展实现。
原则说明2:
开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
Java 代码示例:
假设我们有两个类来计算不同形状的面积:
class Rectangle {
private double width, height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
public double area() {
return width * height;
}
}
class Circle {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
public double area() {
return Math.PI * radius * radius;
}
}
现在,如果需要添加三角形面积计算,不应该修改原有代码,而是通过扩展:
class Triangle {
private double base, height;
public Triangle(double base, double height) {
this.base = base;
this.height = height;
}
public double area() {
return 0.5 * base * height;
}
}
通过增加新的类而不修改现有类,符合开闭原则。
3. 里氏替换原则(Liskov Substitution Principle, LSP)
通俗解释:
子类应该能在任何地方都替代父类,并且表现良好。就好比用不同牌子的手机充电器,你只要插上就能用,而不会影响充电效果,不会突然坏掉。
原则说明1:
里氏替换原则要求子类对象必须能够替换其父类对象,并且程序的行为不会受到影响。即子类必须保持与父类兼容的行为。
原则说明2:
里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
Java 代码示例:
假设我们有一个Bird
类,以及Sparrow
和Penguin
类:
class Bird {
public void fly() {
System.out.println("Bird is flying");
}
}
class Sparrow extends Bird {
@Override
public void fly() {
System.out.println("Sparrow is flying");
}
}
class Penguin extends Bird {
@Override
public void fly() {
System.out.println("Penguin can't fly");
}
}
Penguin
类不能飞,但是Bird
类的定义要求子类能够飞,这违反了里氏替换原则。我们可以将“飞行”的概念抽象为一种行为,避免不适用的行为继承:
abstract class Bird {
public abstract void move();
}
class Sparrow extends Bird {
@Override
public void move() {
System.out.println("Sparrow is flying");
}
}
class Penguin extends Bird {
@Override
public void move() {
System.out.println("Penguin is swimming");
}
}
通过这样设计,每个子类可以按照自己的特性定义“移动”行为,符合LSP。
4. 依赖倒置原则(Dependency Inversion Principle, DIP)
通俗解释:
老板不要直接管理具体的员工,而是通过经理(抽象的接口)来下达指令。这样,员工换了没关系,经理还在,工作还能正常进行。我们应该依赖“接口”而不是“具体实现”。
原则说明1:
依赖倒置原则要求高层模块不应该依赖于低层模块,二者都应该依赖于抽象。这个原则的核心在于使用接口或抽象类,而不是直接依赖具体实现。
原则说明2:
这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
Java 代码示例:
假设我们有一个EmailService
类,它提供发送邮件的功能:
class EmailService {
public void sendEmail(String message) {
System.out.println("Sending email: " + message);
}
}
class Notification {
private EmailService emailService;
public Notification(EmailService emailService) {
this.emailService = emailService;
}
public void notify(String message) {
emailService.sendEmail(message);
}
}
这种设计违反了DIP,因为Notification
类依赖于具体的EmailService
实现。我们可以通过引入接口进行改进:
interface MessageService {
void sendMessage(String message);
}
class EmailService implements MessageService {
@Override
public void sendMessage(String message) {
System.out.println("Sending email: " + message);
}
}
class SMSService implements MessageService {
@Override
public void sendMessage(String message) {
System.out.println("Sending SMS: " + message);
}
}
class Notification {
private MessageService messageService;
public Notification(MessageService messageService) {
this.messageService = messageService;
}
public void notify(String message) {
messageService.sendMessage(message);
}
}
通过依赖接口而不是具体类,我们可以轻松切换不同的消息服务(如SMS或Email),符合依赖倒置原则。
5. 接口隔离原则(Interface Segregation Principle, ISP)
通俗解释:
不要让一个人负责太多不相关的事情,接口也是一样,不要把一大堆不相关的方法放在一个接口里。每个人负责自己该做的事情就好,比如,一个人既要修车又要做饭,显然不合理。
原则说明1:
接口隔离原则要求客户端不应该依赖它不需要的接口。即接口应该尽可能小,确保客户端只依赖于它实际需要的方法。
原则说明2:
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
Java 代码示例:
假设我们有一个Worker
接口,它既包含工作职责,也包含吃饭的职责:
interface Worker {
void work();
void eat();
}
如果有些工人不需要eat
方法,这个设计就违反了接口隔离原则。我们可以将接口分离:
interface Workable {
void work();
}
interface Eatable {
void eat();
}
这样,只有需要吃饭功能的类才会实现Eatable
接口,符合ISP原则。
6. 迪米特法则(Law of Demeter, LoD)
通俗解释:
少打听别人的隐私,保持一定的“距离感”。一个类不需要知道其他类的太多细节,只需要知道自己该怎么用其他类公开的功能就好了,避免太过“亲密”导致耦合度太高。
原则说明1:
迪米特法则要求一个对象应尽量少了解其他对象的细节。也就是说,一个类应该尽量减少与其他类的直接交互,只与必要的对象交互。
原则说明2:
也叫最少知道原则:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
Java 代码示例:
class Engine {
public void start() {
System.out.println("Engine started");
}
}
class Car {
private Engine engine;
public Car() {
engine = new Engine();
}
public void start() {
engine.start();
}
}
在这个例子中,Car
类只与Engine
类交互,并且只使用Engine
提供的公开方法,而不涉及其内部实现细节,符合迪米特法则。
结论
设计模式的六大原则是面向对象设计的基石。通过遵循这些原则,开发者可以编写出更易维护、扩展性更强的代码。理解和应用这些原则对于提高代码质量和降低系统复杂性至关重要。