【问题标题】:Good design pattern to collect data in a complex object在复杂对象中收集数据的良好设计模式
【发布时间】:2025-12-07 10:30:01
【问题描述】:

假设我有一个复杂的对象层次结构,例如:

public class ClassRoom
{
    private ClassRoomInfo classRoomInfo;
    private List<Student> students;
    ...
}

我将此对象作为来自客户端的输入,但该对象可能未完全填充。例如,客户端可以提供非常少的细节,如studentId(s)classRoomInfo 对象中的次要信息。我想为这个对象编写一个DataCollector 类,它将预先收集所有丢失的数据。

在这种情况下,最好的设计模式是什么?

【问题讨论】:

  • “收集所有丢失的数据..”从哪里来?
  • 我有外部服务和数据库,可以从中获取数据。
  • 试试 Builder 模式。它用于构建复杂的对象
  • 使用同一个对象作为从客户端传输数据的容器和需要从数据库中提取数据的业务对象似乎是个坏主意。
  • 您认为DataCollector 是什么样的?

标签: java design-patterns


【解决方案1】:

我认为您需要在需要时延迟加载数据。ClassRoom 类成员的 getter 应该足以满足此目的。

不推荐使用单独的DataCollector 类,因为它会强制您在ClassRoom 中为所有成员公开设置器。

这个link(和书)应该告诉你更多关于延迟加载的信息。

【讨论】:

    【解决方案2】:

    这是一种方法。这个想法是将ClassRoom 视为composite pattern 的一个实例,其中层次结构中的每个对象都知道如何使用DataCollector“填充自己”。如果一个对象已经被填充,它会通过返回自身来响应被要求填充自己的请求;如果未填写,它会从DataCollector 获取必要的信息并构建其自己的类的新实例,该实例已被填写(因此对象本身是不可变的 - 您可能更喜欢让对象自己发生变异,但我通常倾向于使用不可变对象来表示数据)。

    我已经为知道如何填充自己的对象定义了一个接口Fillable。但是,这并不是必需的,因为fillIn 方法永远不会被多态调用。这只是文档,真的。

    我假设填写过程非常简单。显然,它可能更复杂,无论是在检测填写的内容(例如,一个空的学生列表可能表明该列表尚未填充),以及如何填写它。如果您将此设计应用于您的真正的问题,你会发现DataCollector 变得异常复杂。您将需要以不同的方式考虑它;您可能希望将更多的集合逻辑移动到域类中,或者将其拆分为每个类的 DAO(ClassRoomInfoDataCollector 等)。

    public interface Fillable<T> {
        public T fillIn(DataCollector collector);
    }
    
    public class ClassRoom implements Fillable<ClassRoom> {
        private final ClassRoomInfo classRoomInfo;
        private final List<Student> students;
    
        private ClassRoom(ClassRoomInfo classRoomInfo, List<Student> students) {
            this.classRoomInfo = classRoomInfo;
            this.students = students;
        }
    
        @Override
        public ClassRoom fillIn(DataCollector collector) {
            ClassRoomInfo filledInClassRoomInfo = classRoomInfo.fillIn(collector);
            List<Student> filledInStudents = new ArrayList<Student>();
            for (Student student : students) {
                filledInStudents.add(student.fillIn(collector));
            }
            if (filledInClassRoomInfo == classRoomInfo && filledInStudents.equals(students)) return this;
            return new ClassRoom(filledInClassRoomInfo, filledInStudents);
        }
    }
    
    public class ClassRoomInfo implements Fillable<ClassRoomInfo> {
        final String roomNumber;
        final Integer capacity;
    
        private ClassRoomInfo(String roomNumber, int capacity) {
            this.roomNumber = roomNumber;
            this.capacity = capacity;
        }
    
        @Override
        public ClassRoomInfo fillIn(DataCollector collector) {
            if (capacity != null) return this;
            return new ClassRoomInfo(roomNumber, collector.getClassRoomCapacity(roomNumber));
        }
    }
    
    public class Student implements Fillable<Student> {
        final int id;
        final String name;
    
        private Student(int id, String name) {
            this.id = id;
            this.name = name;
        }
    
        @Override
        public Student fillIn(DataCollector collector) {
            if (name != null) return this;
            return new Student(id, collector.getNameOfStudent(id));
        }
    }
    
    public class DataCollector {
        public String getNameOfStudent(int id) {
            ...
        }
    
        public int getClassRoomCapacity(String roomNumber) {
            ...
        }
    }
    

    【讨论】:

    • 非常感谢,这很好!但我有一个疑问。在数据对象中公开 fillIn() 方法是否很好?我认为对象应该独立于这种方法。
    • 我认为这是域逻辑的一部分,因此属于域对象。您可以将其分离为其他对象,但您可能会遇到anaemic domain model 的风险。我可能会相信它不是域逻辑的一部分,但目前,这似乎是最简单的事情。
    【解决方案3】:

    尝试“外观设计模式”。希望它可以帮助你。您可以在 http://javapapers.com/design-patterns/facade-design-pattern/ 中找到有关 Facade 的良好描述

    【讨论】:

    • 您认为这种情况下难以使用的子系统是什么?
    • 据我了解,您可以将 ClassRoom 对象视为您的密钥。所有其他隐式对象,如 Student 或 ClassRoomInfo 作为一个子系统。当客户端请求 ClassRoom 详细信息时,它会向 ClassRoom 内部的多个子系统发送指令并执行一系列操作并完成目标。如果我错了,请告诉我。
    最近更新 更多