设计模式六大原则:面向对象设计的核心

在面向对象编程(OOP)中,设计模式为编写高效、可维护且可扩展的代码提供了重要指导。而这背后的核心是设计模式中的六大原则。本文将详细介绍这些原则,并使用Java代码实例进行说明。


1. 单一职责原则(Single Responsibility Principle, SRP)

通俗解释:

每个人只做自己分内的事,不要什么都想做。一个类只负责一个功能,不要让它承担太多职责。如果一个类既管账又做市场推广,那以后改起来会很麻烦。

原则说明:

单一职责原则要求一个类应该只有一个引起它变化的原因。换句话说,类应该专注于完成一个职责。将多个职责混合在同一个类中会导致类的复杂度增加,维护起来更加困难。

Java 代码示例:

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类中:

java 复制代码
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 代码示例:

假设我们有两个类来计算不同形状的面积:

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;
    }
}

现在,如果需要添加三角形面积计算,不应该修改原有代码,而是通过扩展:

java 复制代码
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类,以及SparrowPenguin类:

java 复制代码
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类的定义要求子类能够飞,这违反了里氏替换原则。我们可以将"飞行"的概念抽象为一种行为,避免不适用的行为继承:

java 复制代码
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类,它提供发送邮件的功能:

java 复制代码
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实现。我们可以通过引入接口进行改进:

java 复制代码
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接口,它既包含工作职责,也包含吃饭的职责:

java 复制代码
interface Worker {
    void work();
    void eat();
}

如果有些工人不需要eat方法,这个设计就违反了接口隔离原则。我们可以将接口分离:

java 复制代码
interface Workable {
    void work();
}

interface Eatable {
    void eat();
}

这样,只有需要吃饭功能的类才会实现Eatable接口,符合ISP原则。


6. 迪米特法则(Law of Demeter, LoD)

通俗解释:

少打听别人的隐私,保持一定的"距离感"。一个类不需要知道其他类的太多细节,只需要知道自己该怎么用其他类公开的功能就好了,避免太过"亲密"导致耦合度太高。

原则说明1:

迪米特法则要求一个对象应尽量少了解其他对象的细节。也就是说,一个类应该尽量减少与其他类的直接交互,只与必要的对象交互。

原则说明2:

也叫最少知道原则:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

Java 代码示例:

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提供的公开方法,而不涉及其内部实现细节,符合迪米特法则。


结论

设计模式的六大原则是面向对象设计的基石。通过遵循这些原则,开发者可以编写出更易维护、扩展性更强的代码。理解和应用这些原则对于提高代码质量和降低系统复杂性至关重要。

相关推荐
动物园首领3 分钟前
Java 中创建线程几种方式
java·java创建线程方式
肖恩聊技术6 分钟前
【2024W34】肖恩技术周刊(第 12 期):热 & 累!
后端·github·aigc·业界资讯
5G微创业9 分钟前
免费制作证件照的小程序源码
java·开发语言·windows
weixin_4218114217 分钟前
归并算法实现
java·算法·排序算法
hmz36021 分钟前
最新绿豆影视系统 /反编译版源码/PC+WAP+APP端 /附搭建教程+软件
前端·后端·php
晨枫阳27 分钟前
Flask项目入门和视图
后端·python·flask
代码江30 分钟前
【速成Redis】04 Redis 概念扫盲:事务、持久化、主从复制、哨兵模式
java·数据库·mysql
jingling55532 分钟前
后端开发刷题 | 最小的K个数(优先队列)
java·开发语言·数据结构·算法
终末圆35 分钟前
探索RESTful风格的网络请求:构建高效、可维护的API接口【后端 20】
java·开发语言·数据库·后端·mysql·算法·restful
路多辛41 分钟前
前后端分离的情况下,后端接口有必要加CSP策略吗?
后端·web安全·安全架构·csp