面向对象的设计原则

Note: This article has been written with the assistance of AI.

单一职责原则(SRP)

一个类应该只有一个引起它变化的原因。

通俗地讲,就是一个类只负责一项职责或功能。不要设计“万能”类,把不相关的功能塞在一起。如果一个类承担了太多的职责,就等于把这些职责耦合在了一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。

核心思想: 高内聚,低耦合。

违反 SRP 的类会带来很多问题:

  1. 可读性差: 一个庞大的类,代码量多,逻辑复杂,难以理解和维护。
  2. 难以维护和修改: 当你需要修改某个功能时,你不得不在这个庞大的类中进行,很容易“牵一发而动全身”,导致意想不到的 Bug。
  3. 难以复用: 你只想复用这个类中的某一个功能,但却不得不把整个“庞然大物”都引入进来。
  4. 稳定性差: 一个职责的变更,可能会影响到其他不相关功能的正常运行。

违反 SRP 的设计

假设我们有一个 User 类,它负责处理用户的所有事情:

// 违反单一职责原则的类
public class User {
    private String name;
    private String email;

    // 构造函数、getter、setter 省略...

    // 职责1:用户属性相关的业务逻辑(这是合理的)
    public boolean isValid() {
        return email != null && email.contains("@");
    }

    // 职责2:数据持久化 - 将用户保存到数据库
    public void saveToDatabase() {
        // 连接数据库,执行 INSERT 语句...
        System.out.println("用户 " + name + " 已保存到数据库。");
    }

    // 职责3:通知 - 发送欢迎邮件
    public void sendWelcomeEmail() {
        // 连接邮件服务器,发送邮件...
        System.out.println("已发送欢迎邮件给 " + email);
    }
}

遵循 SRP 的改进设计

我们将上述三个职责拆分到三个不同的类中:

// 职责1:用户实体模型 - 只负责用户数据和基础验证
public class User {
    private String name;
    private String email;

    // ... 构造函数、getter、setter ...

    public boolean isValid() {
        return email != null && email.contains("@");
    }
}

// 职责2:用户数据访问 - 只负责数据库操作
public class UserRepository {
    public void save(User user) {
        // 连接数据库,执行 INSERT 语句...
        System.out.println("用户 " + user.getName() + " 已保存到数据库。");
    }

    // 还可以有 findById, delete 等方法
}

// 职责3:通知服务 - 只负责发送消息
public class NotificationService {
    public void sendWelcomeEmail(User user) {
        // 连接邮件服务器,发送邮件...
        System.out.println("已发送欢迎邮件给 " + user.getEmail());
    }

    // 未来可以轻松添加新方法,而不影响 User 和 UserRepository
    public void sendSms(User user) {
        // 发送短信的逻辑
    }
}

开放封闭原则(OCP)

软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。

这个原则听起来可能有些矛盾,但它表达了一个非常深刻的思想:

  • 对扩展开放(Open for Extension): 当需求发生变化时,我们可以通过添加新的代码来扩展模块的行为,从而满足新的需求。
  • 对修改关闭(Closed for Modification): 扩展模块行为时,不应该修改原有的、已经测试通过的、正在稳定运行的代码。

核心思想: 通过抽象构建框架,通过实现扩展细节。用抽象定义结构,用具体实现来应对变化。

违反 OCP 会带来严重的问题:

  1. 稳定性风险: 修改已有的、稳定的代码,就像在已经建好的大楼上动地基,极易引入新的 Bug,导致系统变得脆弱。
  2. 测试负担重: 每次修改代码后,都需要对修改过的模块进行全面的回归测试,以确保没有破坏现有功能。
  3. 难以扩展: 系统会变得僵硬,添加新功能变得越来越困难,因为牵一发而动全身。

违反 OCP 的设计

假设我们有一个 AreaCalculator 类,用于计算一组图形的总面积。

// 违反开放封闭原则的设计
public class AreaCalculator {
    // 计算所有图形的总面积
    public double calculateTotalArea(Object[] shapes) {
        double totalArea = 0;
        for (Object shape : shapes) {
            // 问题核心:如果新增一种图形,就必须修改这里的 if-else 逻辑
            if (shape instanceof Rectangle) {
                Rectangle rect = (Rectangle) shape;
                totalArea += rect.getWidth() * rect.getHeight();
            } else if (shape instanceof Circle) {
                Circle circle = (Circle) shape;
                totalArea += Math.PI * circle.getRadius() * circle.getRadius();
            }
            // 如果需要添加三角形,必须在这里添加新的 else if 分支...
            // else if (shape instanceof Triangle) { ... }
        }
        return totalArea;
    }
}

// 矩形类
public class Rectangle {
    private double width;
    private double height;
    // ... 构造函数、getter ...
}

// 圆形类
public class Circle {
    private double radius;
    // ... 构造函数、getter ...
}

遵循 OCP 的改进设计

解决方案是使用抽象。我们创建一个抽象的 Shape 接口,让所有具体的图形类都实现这个接口,并自己负责计算自己的面积。这样,面积计算器就不再需要关心具体的图形类型,它只依赖于一个稳定的抽象。

// 步骤1:定义抽象接口
public interface Shape {
    double calculateArea(); // 所有图形都必须实现这个方法来计算自己的面积
}

// 步骤2:让具体图形类实现接口
public class Rectangle implements Shape {
    private double width;
    private double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public double calculateArea() {
        return width * height;
    }
}

public class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

// 步骤3:重构面积计算器,使其依赖于抽象(Shape),而非具体实现(Rectangle, Circle)
public class AreaCalculator {
    // 现在这个方法对修改是“关闭”的。无论未来有多少新图形,都无需修改它。
    public double calculateTotalArea(List<Shape> shapes) {
        double totalArea = 0;
        for (Shape shape : shapes) {
            // 直接调用抽象方法,多态机制会自动分发到具体实现
            totalArea += shape.calculateArea();
        }
        return totalArea;
    }
}

里氏替换原则(LSP)

所有引用基类(父类)的地方必须能够透明地使用其子类的对象。

更通俗的解释是:子类必须能够完全替换掉它们的父类,并且替换后不会产生任何错误或异常行为。 换句话说,如果你把程序中的父类对象全部替换成它的某个子类对象,程序应该仍然能够正常运行。

这个原则是以计算机科学家 Barbara Liskov 的名字命名的。

违反 LSP 会破坏面向对象的继承体系,导致严重问题:

  1. 破坏多态性: 多态是面向对象的核心特性,而 LSP 是保证多态能够正确工作的基石。如果子类不能替换父类,那么基于父类设计的代码将无法安全地使用子类。
  2. 引发运行时错误: 在替换后,程序可能会抛出异常、产生错误结果或进入异常状态。
  3. 增加代码复杂度: 使用父类对象时,开发者不得不担心具体是哪个子类,需要做类型检查和特殊处理,这违背了面向抽象编程的原则。

里氏替换原则不仅仅是语法上的"is-a"关系,更重要的是行为上的兼容性。"is-a"关系不仅仅是分类学上的,更重要的是行为上的可替换性。。它要求子类在继承父类时:

  • 不强化前置条件(子类对输入的要求不能比父类更严格)
  • 不弱化后置条件(子类对输出的承诺不能比父类更少)
  • 保持父类的约束条件(如不变量)

示例1:违反LSP的经典"正方形/矩形"问题

// 长方形类
class Rectangle {
    private double width;
    private double height;

    public double getWidth() { return width; }
    public double getHeight() { return height; }

    public void setWidth(double width) { this.width = width; }
    public void setHeight(double height) { this.height = height; }

    public double calculateArea() {
        return width * height;
    }
}

// 正方形类 - 从数学上说,正方形"is-a"长方形
class Square extends Rectangle {
    
    @Override
    public void setWidth(double width) {
        // 正方形需要保持宽高相等
        super.setWidth(width);
        super.setHeight(width); // 这里修改了高度!
    }

    @Override
    public void setHeight(double height) {
        // 正方形需要保持宽高相等
        super.setHeight(height);
        super.setWidth(height); // 这里修改了宽度!
    }
}

现在考虑一个使用 Rectangle 的客户端代码:

public class Test {
    // 这个方法期望接收一个Rectangle对象
    public static void testRectangle(Rectangle rect) {
        rect.setWidth(5);
        rect.setHeight(4);
        
        // 根据长方形特性,面积应该是20
        // 但如果传入的是Square对象,面积会是16!
        System.out.println("Expected area: 20, Actual area: " + rect.calculateArea());
        
        // 断言检查 - 对于Square会失败
        assert rect.calculateArea() == 20 : "违反长方形行为约定!";
    }

    public static void main(String[] args) {
        Rectangle rect = new Rectangle();
        testRectangle(rect); // 正常工作:面积=20
        
        Rectangle square = new Square(); // 用子类替换父类
        testRectangle(square); // 出现问题:面积=16!
    }
}

Square 虽然语法上继承自 Rectangle,但它的行为与 Rectangle 的约定不一致。RectanglesetWidth()setHeight() 应该是独立操作的,而 Square 修改了这个行为。因此,Square 不能透明地替换 Rectangle

对于"正方形/矩形"问题,更好的设计是不要使用继承:

// 使用组合而不是继承
interface Shape {
    double calculateArea();
}

class Rectangle implements Shape {
    private double width;
    private double height;
    
    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }
    
    @Override
    public double calculateArea() {
        return width * height;
    }
    
    // getter方法...
}

class Square implements Shape {
    private double side;
    
    public Square(double side) {
        this.side = side;
    }
    
    @Override
    public double calculateArea() {
        return side * side;
    }
    
    // getter方法...
}

示例2:违反LSP的"企鹅/鸟"问题

// 鸟类
class Bird {
    public void fly() {
        System.out.println("I can fly");
    }
    
    public void eat() {
        System.out.println("I can eat");
    }
}

// 燕子类
class Swallow extends Bird {
    // 燕子会飞,符合Bird的行为
}

// 企鹅类 - 从生物学上说,企鹅"is-a"鸟
class Penguin extends Bird {
    @Override
    public void fly() {
        // 但企鹅不会飞!这里抛出异常或什么都不做都违反了LSP
        throw new UnsupportedOperationException("Penguins can't fly!");
    }
}

考虑使用 Bird 的客户端代码:

public class BirdTest {
    public static void makeBirdFly(Bird bird) {
        // 这个方法期望所有Bird都能fly
        bird.fly(); // 如果传入Penguin,这里会抛出异常!
    }

    public static void main(String[] args) {
        Bird swallow = new Swallow();
        makeBirdFly(swallow); // 正常工作
        
        Bird penguin = new Penguin(); // 用子类替换父类
        makeBirdFly(penguin); // 抛出异常!违反LSP
    }
}

对于"企鹅/鸟"问题,通过接口隔离来遵循LSP:

// 细化的接口
interface Bird {
    void eat();
}

interface Flyable {
    void fly();
}

interface Swimmable {
    void swim();
}

// 燕子实现Flyable
class Swallow implements Bird, Flyable {
    @Override
    public void eat() { System.out.println("Swallow eating"); }
    
    @Override
    public void fly() { System.out.println("Swallow flying"); }
}

// 企鹅实现Swimmable但不实现Flyable
class Penguin implements Bird, Swimmable {
    @Override
    public void eat() { System.out.println("Penguin eating"); }
    
    @Override
    public void swim() { System.out.println("Penguin swimming"); }
}

现在客户端可以安全地使用:

public class BirdTest {
    public static void makeBirdFly(Flyable flyableBird) {
        flyableBird.fly(); // 这里保证传入的对象一定能fly
    }
    
    public static void makeBirdSwim(Swimmable swimmableBird) {
        swimmableBird.swim(); // 这里保证传入的对象一定能swim
    }
}

应用 LSP 总结

  1. 子类方法的前置条件不能强于父类:子类对输入参数的要求不能比父类更严格。
  2. 子类方法的后置条件不能弱于父类:子类对输出结果的承诺不能比父类更少。
  3. 子类不应该抛出父类没有声明的异常:或者抛出的异常应该是父类异常的子类。
  4. 子类应该保持父类的约束条件:如某些字段的取值范围等。
  5. 当发现需要频繁进行类型检查时(如 if (obj instanceof SomeSubclass)),这通常是违反LSP的信号。

接口隔离原则(ISP)

客户端不应该被迫依赖于它不使用的接口。或者说,一个类对另一个类的依赖应该建立在最小的接口上。

通俗地讲:不要创建庞大臃肿的接口,而应该将接口拆分成更小、更具体的接口,这样客户端只需要知道它们感兴趣的方法。

违反 ISP 会带来很多问题:

  1. 接口污染: 客户端被迫实现它们根本不需要的方法,这违反了单一职责原则。
  2. 代码冗余: 对于不需要的方法,客户端通常只能提供空实现或抛出异常,这会产生大量无用的代码。
  3. 耦合性增加: 客户端与不需要的方法产生了依赖,当接口发生变化时,即使客户端不关心那些方法,也不得不重新编译和部署。
  4. 难以理解和维护: 庞大的接口难以理解,也很难知道一个类到底实现了哪些真正有用的功能。

违反 ISP 的设计

假设我们有一个庞大的 Animal 接口,包含了所有动物可能具有的行为:

// 一个"臃肿"的接口,违反了接口隔离原则
public interface Animal {
    void eat();
    void sleep();
    void fly();   // 问题:不是所有动物都会飞
    void swim();  // 问题:不是所有动物都会游泳
    void run();   // 问题:不是所有动物都会跑
}

// 鸟类实现 - 被迫实现所有方法
public class Bird implements Animal {
    @Override
    public void eat() {
        System.out.println("Bird is eating");
    }
    
    @Override
    public void sleep() {
        System.out.println("Bird is sleeping");
    }
    
    @Override
    public void fly() {
        System.out.println("Bird is flying"); // 鸟确实会飞
    }
    
    @Override
    public void swim() {
        // 但大多数鸟不会游泳!被迫提供空实现或抛出异常
        // throw new UnsupportedOperationException("Birds don't swim!");
        // 或者什么都不做,但这违反了"最小惊讶原则"
    }
    
    @Override
    public void run() {
        System.out.println("Bird is running");
    }
}

// 鱼类实现 - 同样被迫实现所有方法
public class Fish implements Animal {
    @Override
    public void eat() {
        System.out.println("Fish is eating");
    }
    
    @Override
    public void sleep() {
        System.out.println("Fish is sleeping");
    }
    
    @Override
    public void fly() {
        // 鱼根本不会飞!被迫提供无意义的实现
        throw new UnsupportedOperationException("Fish can't fly!");
    }
    
    @Override
    public void swim() {
        System.out.println("Fish is swimming"); // 鱼确实会游泳
    }
    
    @Override
    public void run() {
        // 鱼不会跑!
        throw new UnsupportedOperationException("Fish can't run!");
    }
}

遵循 ISP 的改进设计

// 拆分成多个专门的接口

// 基本生物接口
public interface LivingBeing {
    void eat();
    void sleep();
}

// 飞行能力接口
public interface Flyable {
    void fly();
}

// 游泳能力接口  
public interface Swimmable {
    void swim();
}

// 奔跑能力接口
public interface Runnable {
    void run();
}

与其提供一个什么都做的万能接口,不如提供多个专注的专用接口。

依赖倒置原则(DIP)

它的核心定义包含两个部分:

  1. 高层模块不应该依赖于低层模块,二者都应该依赖于抽象
  2. 抽象不应该依赖于细节,细节应该依赖于抽象

这里的"倒置"指的是与传统依赖方向的对比。在传统设计中,高层模块直接依赖低层模块,而DIP将这种依赖关系"倒置"了。

违反 DIP 会导致的问题:

  1. 紧耦合:高层模块与低层模块紧密耦合,难以独立变化
  2. 难以测试:无法轻松替换依赖进行单元测试
  3. 难以扩展:添加新功能需要修改高层模块代码
  4. 违反开闭原则:对扩展不开放,对修改不关闭
  • 高层模块:包含核心业务逻辑、策略和应用程序规则的模块
  • 低层模块:包含具体实现、工具函数、基础设施细节的模块
  • 抽象:接口或抽象类,定义契约而不涉及具体实现

违反 DIP 的设计

// 低层模块 - 具体实现
public class EmailService {
    public void sendEmail(String message, String recipient) {
        System.out.println("发送邮件给 " + recipient + ": " + message);
        // 实际的邮件发送逻辑...
    }
}

public class SmsService {
    public void sendSms(String message, String phoneNumber) {
        System.out.println("发送短信给 " + phoneNumber + ": " + message);
        // 实际的短信发送逻辑...
    }
}

// 高层模块 - 业务逻辑
public class NotificationService {
    // 问题:高层模块直接依赖于低层模块的具体实现
    private EmailService emailService;
    private SmsService smsService;
    
    public NotificationService() {
        // 紧耦合:在构造函数中直接创建具体实例
        this.emailService = new EmailService();
        this.smsService = new SmsService();
    }
    
    public void sendNotification(String message, String user, String type) {
        if ("email".equals(type)) {
            emailService.sendEmail(message, user);
        } else if ("sms".equals(type)) {
            smsService.sendSms(message, user);
        }
    }
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        NotificationService notification = new NotificationService();
        notification.sendNotification("Hello", "user@example.com", "email");
    }
}

遵循 DIP 的改进设计

// 步骤1:定义抽象(接口)
public interface MessageService {
    void send(String message, String recipient);
    boolean supports(String type); // 支持的消息类型
}

// 步骤2:低层模块实现抽象
public class EmailService implements MessageService {
    @Override
    public void send(String message, String recipient) {
        System.out.println("发送邮件给 " + recipient + ": " + message);
    }
    
    @Override
    public boolean supports(String type) {
        return "email".equals(type);
    }
}

public class SmsService implements MessageService {
    @Override
    public void send(String message, String recipient) {
        System.out.println("发送短信给 " + recipient + ": " + message);
    }
    
    @Override
    public boolean supports(String type) {
        return "sms".equals(type);
    }
}

public class WeChatService implements MessageService {
    @Override
    public void send(String message, String recipient) {
        System.out.println("发送微信消息给 " + recipient + ": " + message);
    }
    
    @Override
    public boolean supports(String type) {
        return "wechat".equals(type);
    }
}

// 步骤3:高层模块依赖于抽象
public class NotificationService {
    // 高层模块依赖于抽象(接口),而不是具体实现
    private List<MessageService> messageServices;
    
    // 通过构造函数注入依赖 - 这就是依赖注入(DI)
    public NotificationService(List<MessageService> messageServices) {
        this.messageServices = messageServices;
    }
    
    public void sendNotification(String message, String recipient, String type) {
        for (MessageService service : messageServices) {
            if (service.supports(type)) {
                service.send(message, recipient);
                return;
            }
        }
        throw new IllegalArgumentException("不支持的消息类型: " + type);
    }
    
    // 可以轻松添加新功能而不修改现有代码
    public void broadcast(String message, String type) {
        for (MessageService service : messageServices) {
            if (service.supports(type)) {
                service.send(message, "all users");
            }
        }
    }
}

依赖注入的三种方式

构造函数注入

public class NotificationService {
    private final MessageService messageService;
    
    // 依赖通过构造函数注入
    public NotificationService(MessageService messageService) {
        this.messageService = messageService;
    }
}

Setter方法注入

public class NotificationService {
    private MessageService messageService;
    
    // 依赖通过setter方法注入
    public void setMessageService(MessageService messageService) {
        this.messageService = messageService;
    }
}

接口注入

public interface MessageServiceAware {
    void setMessageService(MessageService messageService);
}

public class NotificationService implements MessageServiceAware {
    private MessageService messageService;
    
    @Override
    public void setMessageService(MessageService messageService) {
        this.messageService = messageService;
    }
}

最少知识原则 (Law of Demeter)

一个对象应该对其他对象有最少的了解。或者说,只与直接的朋友通信。

通俗地讲:不要和陌生人说话,只和你的直接朋友通信。

这里的"朋友"指的是:

  • 当前对象本身 (this)
  • 以参数形式传入到当前对象方法中的对象
  • 当前对象的成员变量
  • 如果当前对象的成员变量是一个集合,那么集合中的元素也是朋友
  • 当前对象所创建的对象

违反 LoD 会带来很多问题:

  1. 耦合度增高:对象之间关系复杂,形成蜘蛛网式的依赖
  2. 难以维护:一个类的修改会影响到很多其他类
  3. 难以复用:类之间紧密耦合,难以单独复用
  4. 可测试性差:需要构建复杂的依赖关系才能进行测试

核心规则:"只使用一个点"

一个简单的判断方法是:在方法中,应该尽量避免使用多个连续的.(点)来访问对象。通常,只使用一个点是相对安全的。

违反 LoD 的设计

// 员工类
public class Employee {
    private String name;
    private Department department;
    
    public Employee(String name, Department department) {
        this.name = name;
        this.department = department;
    }
    
    // getter方法
    public String getName() { return name; }
    public Department getDepartment() { return department; }
}

// 部门类
public class Department {
    private String name;
    private Company company;
    
    public Department(String name, Company company) {
        this.name = name;
        this.company = company;
    }
    
    // getter方法
    public String getName() { return name; }
    public Company getCompany() { return company; }
}

// 公司类
public class Company {
    private String name;
    private Address address;
    
    public Company(String name, Address address) {
        this.name = name;
        this.address = address;
    }
    
    // getter方法
    public String getName() { return name; }
    public Address getAddress() { return address; }
}

// 地址类
public class Address {
    private String city;
    private String street;
    
    public Address(String city, String street) {
        this.city = city;
        this.street = street;
    }
    
    // getter方法
    public String getCity() { return city; }
    public String getStreet() { return street; }
}

// 报告生成类 - 违反迪米特法则!
public class ReportGenerator {
    // 问题:这个方法知道了太多它不应该知道的细节
    public void generateEmployeeReport(Employee employee) {
        // 违反LoD:使用了多个连续的点,深入了解了Employee的内部结构
        System.out.println("员工姓名: " + employee.getName());
        System.out.println("部门: " + employee.getDepartment().getName()); // 第一个点之后又一个点
        System.out.println("公司: " + employee.getDepartment().getCompany().getName()); // 两个连续的点
        System.out.println("公司地址: " + employee.getDepartment().getCompany().getAddress().getCity()); // 三个连续的点!
    }
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        Address address = new Address("北京", "海淀区中关村");
        Company company = new Company("某科技公司", address);
        Department department = new Department("技术部", company);
        Employee employee = new Employee("张三", department);
        
        ReportGenerator generator = new ReportGenerator();
        generator.generateEmployeeReport(employee);
    }
}
  • ReportGenerator 知道了 EmployeeDepartmentCompanyAddress 的所有内部结构
  • 如果 Company 类的结构发生变化(比如把 address 字段改名),ReportGenerator 也需要修改
  • 形成了紧密的耦合,ReportGenerator 依赖于整个对象链

遵循 LoD 的改进设计

// 员工类 - 添加业务方法,封装内部细节
public class Employee {
    private String name;
    private Department department;
    
    public Employee(String name, Department department) {
        this.name = name;
        this.department = department;
    }
    
    public String getName() { return name; }
    
    // 提供业务方法,而不是简单的getter
    public String getDepartmentName() {
        return department.getDepartmentName();
    }
    
    public String getCompanyName() {
        return department.getCompanyName();
    }
    
    public String getCompanyCity() {
        return department.getCompanyCity();
    }
    
    // 或者提供一个综合的方法
    public String getWorkInfo() {
        return String.format("%s在%s的%s工作", 
            name, getCompanyCity(), getDepartmentName());
    }
}

// 部门类 - 同样封装内部细节
public class Department {
    private String name;
    private Company company;
    
    public Department(String name, Company company) {
        this.name = name;
        this.company = company;
    }
    
    public String getDepartmentName() { return name; }
    
    // 封装对Company的访问
    public String getCompanyName() {
        return company.getCompanyName();
    }
    
    public String getCompanyCity() {
        return company.getCompanyCity();
    }
}

// 公司类 - 封装内部细节
public class Company {
    private String name;
    private Address address;
    
    public Company(String name, Address address) {
        this.name = name;
        this.address = address;
    }
    
    public String getCompanyName() { return name; }
    
    // 封装对Address的访问
    public String getCompanyCity() {
        return address.getCity();
    }
}

// 地址类保持不变
public class Address {
    private String city;
    private String street;
    
    public Address(String city, String street) {
        this.city = city;
        this.street = street;
    }
    
    public String getCity() { return city; }
    public String getStreet() { return street; }
}

// 改进的报告生成类 - 遵循 LoD
public class ReportGenerator {
    // 现在这个方法只与直接朋友(Employee)通信
    public void generateEmployeeReport(Employee employee) {
        System.out.println("员工姓名: " + employee.getName());
        System.out.println("部门: " + employee.getDepartmentName()); // 只有一个点
        System.out.println("公司: " + employee.getCompanyName());   // 只有一个点
        System.out.println("工作地点: " + employee.getCompanyCity()); // 只有一个点
        
        // 或者使用综合方法
        System.out.println("工作信息: " + employee.getWorkInfo());
    }
}

外观模式

// 员工信息服务(外观类)
public class EmployeeInfoService {
    private Employee employee;
       
    public EmployeeInfoService(Employee employee) {
        this.employee = employee;
    }
    
    public String getBasicInfo() {
        return employee.getName();
    }
    
    public String getWorkInfo() {
        return String.format("在%s的%s部门工作", 
            employee.getCompanyCity(), employee.getDepartmentName());
    }
    
    public String getFullReport() {
        return String.format("员工%s,在%s的%s部门,属于%s公司",
            employee.getName(),
            employee.getCompanyCity(),
            employee.getDepartmentName(),
            employee.getCompanyName());
    }
}

// 简化的报告生成类
public class ReportGenerator {
    public void generateEmployeeReport(EmployeeInfoService infoService) {
        System.out.println("基本信息: " + infoService.getBasicInfo());
        System.out.println("工作信息: " + infoService.getWorkInfo());
        System.out.println("完整报告: " + infoService.getFullReport());
    }
}

让类保持"害羞" - 不要暴露太多内部细节,也不要打听太多别人的事情。

posted @ 2025-09-29 12:17  Charlie_Byte  阅读(9)  评论(0)    收藏  举报