OOP设计的五大原则

面向对象程序设计的五大原则

1. 单一职责原则

就一个类而言, 应该仅有一个引起它变化的原因

如果一个类承担的职责太多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其它职责能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。如果能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责。下面举一个单一职责原则的示例。

1.1 违反单一职责原则的示例

定义 OrderManager 类

class OrderManager {
    public void processOrder(Order order) {
        // 订单处理逻辑
        System.out.println("Processing order: " + order);
    }

    public void sendEmailNotification(Customer customer, String message) {
        // 发送电子邮件的逻辑
        System.out.println("Sending email notification to " + customer.getEmail() + ": " + message);
    }
}

定义 Order 类和 Customer 类

class Order {
    private String orderId;
    public Order(String orderId) {
        this.orderId = orderId;
    }

    public String getOrderId() {
        return orderId;
    }
}

class Customer {
    private String email;
    // 其他属性和方法
    public Customer(String email) {
        this.email = email;
    }
    public String getEmail() {
        return email;
    }
}

编写测试用例:

public class Main {
    public static void main(String[] args) {
        Order order = new Order("ORD12345");
        Customer customer = new Customer("alice@example.com");

        OrderManager orderManager = new OrderManager();
        orderManager.processOrder(order);
        orderManager.sendEmailNotification(customer, "Your order has been processed.");
    }
}

运行结果如下:

Processing order: designPattern.strategy.Order@568db2f2
Sending email notification to alice@example.com: Your order has been processed.

1.2 改造用例

为了遵循单一职责原则,我们需要将 OrderManager 类中的职责分离,将其拆分为两个类:一个专门处理订单,另一个专门负责发送电子邮件。

定义 OrderProcessor 类:

class OrderProcessor {
    public void processOrder(Order order) {
        // 订单处理逻辑
        System.out.println("Processing order: " + order);
    }
}

定义 EmailNotifier

class EmailNotifier {
    public void sendEmailNotification(Customer customer, String message) {
        // 发送电子邮件的逻辑
        System.out.println("Sending email notification to " + customer.getEmail() + ": " + message);
    }
}

修改测试代码:

public class Main {
    public static void main(String[] args) {
        Order order = new Order("ORD12345");
        Customer customer = new Customer("alice@example.com");

        OrderProcessor orderProcessor = new OrderProcessor();
        orderProcessor.processOrder(order);

        EmailNotifier emailNotifier = new EmailNotifier();
        emailNotifier.sendEmailNotification(customer, "Your order has been processed.");
    }
}

运行结果如下:

Processing order: designPattern.strategy.Order@568db2f2
Sending email notification to alice@example.com: Your order has been processed.

2. 开放――封闭原则

软件实体可以扩展,但是不可修改。即对于扩展是开放的,对于修改是封闭的。面 对需求,对程序的改动是通过增加代码来完成的,而不是改动现有的代码。下面我们举一个例子:

假设我们有一个应用程序,需要处理不同类型的支付方式,如信用卡支付、支付宝支付等。我们希望在不修改现有代码的前提下,能够轻松地添加新的支付方式。

2.1 违反开放封闭原则的代码

定义 PaymentProcessor

class PaymentProcessor {
    public void processPayment(Payment payment) {
        if (payment.getType().equals("credit_card")) {
            processCreditCardPayment(payment);
        } else if (payment.getType().equals("alipay")) {
            processAlipayPayment(payment);
        } else {
            throw new IllegalArgumentException("Unsupported payment type: " + payment.getType());
        }
    }

    private void processCreditCardPayment(Payment payment) {
        // 处理信用卡支付的逻辑
        System.out.println("Processing credit card payment: " + payment.getAmount());
    }

    private void processAlipayPayment(Payment payment) {
        // 处理支付宝支付的逻辑
        System.out.println("Processing Alipay payment: " + payment.getAmount());
    }
}

定义 Payment:

// 省略 get, set, 构造等方法
public class Payment {
    private double amount;
    private String type;
}

编写测试代码:

public class Main {
    public static void main(String[] args) {
        Payment creditCardPayment = new Payment(100, "credit_card");
        Payment alipayPayment = new Payment(200, "alipay");

        PaymentProcessor processor = new PaymentProcessor();
        processor.processPayment(creditCardPayment);
        processor.processPayment(alipayPayment);
    }
}

2.2 改造示例

为了遵循开放封闭原则,我们需要通过扩展现有代码而不是修改现有代码的方式来添加新的支付方式。为此,我们可以定义一个支付接口 PaymentStrategy 和具体的支付策略类。

定义支付接口 PaymentStrategy

public interface PaymentStrategy {
    void processPayment(Payment payment);
}

定义信用卡支付具体策略:

class CreditCardPaymentStrategy implements PaymentStrategy {
    @Override
    public void processPayment(Payment payment) {
        // 处理信用卡支付的逻辑
        System.out.println("Processing credit card payment: " + payment.getAmount());
    }
}

定义支付宝支付具体策略:

class AlipayPaymentStrategy implements PaymentStrategy {
    @Override
    public void processPayment(Payment payment) {
        // 处理支付宝支付的逻辑
        System.out.println("Processing Alipay payment: " + payment.getAmount());
    }
}

定义 PaymentProcessor 类,使用策略模式

class PaymentProcessor {
    private Map<String, PaymentStrategy> strategies;

    public PaymentProcessor() {
        strategies = new HashMap<>();
        strategies.put("credit_card", new CreditCardPaymentStrategy());
        strategies.put("alipay", new AlipayPaymentStrategy());
    }

    public void processPayment(Payment payment) {
        PaymentStrategy strategy = strategies.get(payment.getType());
        if (strategy == null) {
            throw new IllegalArgumentException("Unsupported payment type: " + payment.getType());
        }
        strategy.processPayment(payment);
    }
}

改进主方法进行测试:

public class Main {
    public static void main(String[] args) {
        Payment creditCardPayment = new Payment(100, "credit_card");
        Payment alipayPayment = new Payment(200, "alipay");

        PaymentProcessor processor = new PaymentProcessor();
        processor.processPayment(creditCardPayment);
        processor.processPayment(alipayPayment);
    }
}

添加新的支付方式,例如微信支付:

class WeChatPayPaymentStrategy implements PaymentStrategy {
    @Override
    public void processPayment(Payment payment) {
        // 处理微信支付的逻辑
        System.out.println("Processing WeChat Pay payment: " + payment.getAmount());
    }
}

PaymentProcessor 类中注册新的支付策略:

public class PaymentProcessor {
    private Map<String, PaymentStrategy> strategies;

    public PaymentProcessor() {
        strategies = new HashMap<>();
        strategies.put("credit_card", new CreditCardPaymentStrategy());
        strategies.put("alipay", new AlipayPaymentStrategy());
        strategies.put("wechat_pay", new WeChatPayPaymentStrategy()); // 新增的支付方式
    }

    // ... 其他代码保持不变 ...
}

修改主方法:

public class Main {
    public static void main(String[] args) {
        Payment creditCardPayment = new Payment(100, "credit_card");
        Payment alipayPayment = new Payment(200, "alipay");
        Payment wechatPayPayment = new Payment(300, "wechat_pay");

        PaymentProcessor processor = new PaymentProcessor();
        processor.processPayment(creditCardPayment);
        processor.processPayment(alipayPayment);
        processor.processPayment(wechatPayPayment);
    }
}

在这个改进的设计中,PaymentProcessor 类通过使用策略模式来处理不同的支付方式。新的支付方式可以通过扩展策略类来实现,而无需修改 PaymentProcessor 类的实现代码。这样做的好处包括:

  • 可扩展性:新的支付方式可以很容易地添加进来,而不影响现有的代码。
  • 可维护性:每个支付方式的处理逻辑都在单独的类中实现,使得代码更易于维护。
  • 可测试性:每个支付策略类都可以独立进行单元测试,提高了测试的覆盖率和有效性。

通过这个例子可以看出,开放封闭原则通过设计可扩展的架构,使得在不修改现有代码的情况下可以轻松地添加新的功能,从而提高了系统的灵活性和可维护性。

3. 里氏替换原则

子类对象必须能够替换父类对象的工作职责,必须保证原有的程序逻辑不变以及正确执行。换句话说,如果一个类型是另一个类型的子类型,那么它应该在不改变程序正确性的前提下,代替其父类型的所有出现。这一原则的思想是保持继承层次结构中各个类的一致性。

里氏替换原则的意义:

  • 提高代码的复用性:通过保证子类可以替换基类,可以更方便地复用基类的代码。
  • 增强代码的健壮性:遵守里氏替换原则有助于编写出更加健壮的代码,减少因不当的继承或子类覆盖方法而导致的错误。
  • 降低系统的耦合度:里氏替换原则鼓励我们设计接口和抽象类时尽量考虑通用的功能,减少对特定实现的依赖。

下面举一个不遵守里氏替换原则的例子:

假设有一个基类 Bird 和两个子类 Penguin 和 Parrot。Bird类有一个方法fly()。如果我们按照面向对象的常规做法,PenguinParrot都继承自Bird类,那么fly()方法应该在Parrot中正常工作,但是在Penguin` 中可能就不适用了,因为企鹅不能飞行。

如果不遵守里氏替换原则,可能会有如下的代码:

public class Main {
    public static void main(String[] args) {

    }
}

// 基类: 鸟类
class Bird {
    public void fly(){
        // 飞行的实现
        Bird bird = new Penguin();
        bird.fly(); // 这里调用
    }
}

// 鹦鹉
class Parrot extends Bird {
    @Override
    public void fly() {
        System.out.println("鹦鹉在飞...");
    }
}

// 企鹅
class Penguin extends Bird {
    // 企鹅不能飞行,所以这里的实现实际上不会调用fly()
//    @Override
//    public void fly() {
//        System.out.println("企鹅并不会飞!");
//    }
}

为了避免这种情况,我们应该重新设计类的结构,例如将 fly() 方法改为可选的行为,或者在 Bird 类中提供一个默认的空实现,或者根本不提供 fly() 方法,而是让子类自己决定是否实现飞行的能力。

需要注意的是:里氏替换原则强调了继承关系中的子类应该能够无缝地替换其父类对象,并且这种替换不会导致程序行为的改变。这有助于确保在使用继承时,代码的健壮性和可维护性。在实际开发过程中,我们应该谨慎地使用继承,并且确保子类能够遵守这一原则。

4. 依赖倒置原则

依赖倒置原则认为,高层模块不应该依赖于底层模块,二者都应该依赖于抽象或接口;抽象不应该依赖于实现,实现才能依赖于抽象,换句话说,抽象决定实现,但是实现不能决定

抽象中的定义。下面来讲一个依赖倒置原则的例子:

假设我们有一个应用程序需要处理订单,并且需要将订单信息保存到数据库中。为了演示依赖倒置原则,我们将创建一个 OrderService 类,它负责处理订单,并且依赖于一个 IDataAccess 接口来完成数据的持久化。

首先定义一个抽象的数据访问接口:

interface IDataAccess {
    void save(Order order);
}

定义两个具体的数据库访问类,分别是MySQLDataAccess和RedisDataAccess:

class MySQLDataAccess implements IDataAccess {

    @Override
    public void save(Order order) {
        System.out.println("Saving order data into MySQL database: " + order);
    }
}

class RedisDataAccess implements IDataAccess {

    @Override
    public void save(Order order) {
        System.out.println("Saving order data into Redis database: " + order);
    }
}

定义一个订单类

class Order {
    private int id; // 订单id
    private String customerName;    // 顾客名
    // 省略 get, set, 构造,toString()等方法
}

创建一个 OrderService 类,该类依赖于 IDataAccess 接口:

class OrderService {
    private IDataAccess dataAccess;

    private OrderService(IDataAccess dataAccess) {
        this.dataAccess = dataAccess;
    }

    // 处理订单的逻辑
    public void processOrder(Order order) {
        System.out.println("Processing order: " + order);
        dataAccess.save(order); // 使用抽象接口保存订单
    }
}

编写测试用例进行测试:

public class Main {
    public static void main(String[] args) {
        Order order = new Order(1, "cherry");
        // 使用MySQL保存数据
        OrderService orderService0 = new OrderService(new MySQLDataAccess());
        orderService0.processOrder(order);

        // 使用Redis保存数据
        OrderService orderService1 = new OrderService(new RedisDataAccess());
        orderService1.processOrder(order);
    }
}

结果如下:

Processing order: Order{id=1, customerName='cherry'}
Saving order data into MySQL database: Order{id=1, customerName='cherry'}
Processing order: Order{id=1, customerName='cherry'}
Saving order data into Redis database: Order{id=1, customerName='cherry'}

在这个例子中,OrderService 类依赖于 IDataAccess 接口,而不是具体的数据库实现类(如 MySQLDataAccessMongoDBDataAccess)。这意味着,如果将来需要更换数据库实现,只需要更换 OrderService 构造函数中的参数即可,而不需要修改 OrderService 的实现代码。这样就实现了高层模块(OrderService)对低层模块(具体数据库实现)的解耦。

此外,通过依赖抽象而不是具体实现,我们可以在不改变现有代码的情况下,很容易地扩展或替换底层实现,提高了系统的可维护性和可扩展性。这就是依赖倒置原则所带来的好处。

5. 接口隔离原则

接口隔离原则指出,不应该强迫客户依赖它们不使用的方法。也就是说,应该将接口细化,让客户只需要知道它们需要使用的那部分接口,而不是一个庞大的接口集合。下面举一个例子:

假设我们有一个应用程序,其中包含了各种机器,如打印机、扫描仪和复印机。每种机器都有自己的功能,例如打印机可以打印文档,扫描仪可以扫描文档,而复印机既可以打印也可以扫描文档。

首先定义一个通用的机器接口IMachine

// 通用的机器接口
interface IMachine {
    void print(Document document);      // 打印文档
    void scan(Document document);       // 扫描文档
    void copy(Document document);		// 复印文档
}

定义一个文档Document:

class Document { }

定义专门用于打印的接口IPrinter:

interface IPrinter {
    void print(Document document);
}

定义专门用于扫描的接口IScanner

interface IScanner {
    void scan(Document document);
}

定义专门用于复印的接口ICopier

interface IPrinter {
    void print(Document document);
}

实现一个打印机Printer

class Printer implements IPrinter {

    @Override
    public void print(Document document) {
        System.out.println("Printing document: " + document);
    }
}

实现一个扫描器Scanner:

class Scanner implements IScanner {

    @Override
    public void scan(Document document) {
        System.out.println("Scanning document: " + document);
    }
}

实现一个复印机Copier

class Copier implements IPrinter, IScanner {

    @Override
    public void print(Document document) {
        System.out.println("Printing document: " + document);
    }

    @Override
    public void scan(Document document) {
        System.out.println("Scanning document: " + document);
    }
}

编写主方法进行测试:

public class Main {
    public static void main(String[] args) {
        Document doc = new Document();
        Printer printer = new Printer();
        printer.print(doc);

        Scanner scanner = new Scanner();
        scanner.scan(doc);

        Copier copier = new Copier();
        copier.print(doc);
    }
}

测试结果如下:

Printing document: designPattern.strategy.Document@568db2f2
Scanning document: designPattern.strategy.Document@568db2f2
Printing document: designPattern.strategy.Document@568db2f2
posted @   LilyFlower  阅读(46)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示