CRUD 方法应该是对象的一部分还是服务层?

Should CRUD methods be part of the object or a service layer?

我正在学习一些面向对象的设计问题,我遇到了一个图书目录中需要 Book 对象的问题。这是问题解决方案中建议的 Book 对象。

public class Book {

    private long ID;
    private String details; 
    private static Set<Book> books;

    public Book(long iD, String details) { ... }    

    public static void addBook(long iD, String details){
        books.add(new Book(iD, details));
    }

    public void update() { }

    public static void delete(Book b) { books.remove(b); }

    public static Book find(long id){
        for (Book b : books)        
            if(b.getID() == id) return b;   
        return null;

    }
}

从某种意义上说,这个 Book 对象对我来说非常好,因为它包含 modify/get 有关书籍对象的信息以及有关书籍的数据所需的所有方法。因此,当使用 OOP 的定义时,这看起来很棒,因为这就是对象应该的样子。

但是我在 1-2 年的编程生涯中做事的方式我一直认为是创建、删除。修改一个对象应该通过一个服务层来完成,本质上是一个 BookService class 在这种情况下,它包含创建书籍的方法,更新书籍和使用不包含这些 CRUD 方法的 Book 对象从数据库中删除书籍。

第一种方法在理论上看起来很棒,而根据我的经验,下一种方法在实践中非常棒。第二种方法有flaws/pitfalls吗?应该首选哪种方法?

PS:我不确定这样的问题是否会被接受,如果不被接受我会很乐意 delete/edit 但我找不到更好的地方得到答案:(

如果您正在实现一个用于学习目的的控制台应用程序,那么如果您将 CRUD 逻辑实现到模型中,这没什么大不了的。但我认为情况并非如此。

您实现的这个模型 Book 必须只有对象属性加上 getter 和 setter。您实现的其他 CRUD 方法必须在外部层中。外部层可能是 SERVICE 或 DAO,这取决于.. 但是你必须知道,如果你像现在这样在模型 类 中编写一些额外的逻辑,这不是一个好的做法。

您的 Book 对象是所谓的 "domain object"。它的唯一职责就是提供所谓的"business logic"。例如:它有一个状态,由 class 成员定义,它可以包含 class 方法(用于计算等)与状态交互。没有其他对象应该知道内部业务逻辑实现。

现在,领域对象也被称为 "models"。但这可能有点令人困惑。为什么?因为一个"model"其实就是一层。它由三个子层组成:

  • domain layer (domain model)组成的域 对象。通过它们的结构和相互依赖, 它们是现实世界(商业)的抽象 objects/entities。该层还可以包含类似的结构 领域对象的集合。
  • 存储层,由classes组成,负责 域对象的传输 into/from 底层存储 系统(可能是 RDBMS、文件系统等):repositories(data) 映射器adapters、数据访问抽象classes 等。 使用这些结构也达到了制作域的目的 对象(完全)不可知,不知道存储类型和 它的处理方式。
  • service layer 是从 classes(例如服务)构建的 执行涉及上两个结构的操作 子层。用户与程序的交互应该发生 只能通过服务。

因此,在您的情况下,域对象 Book 将如下所示:

public class Book {

    private long ID;
    private String details;

    public Book() { ... }

    // Setters/getters...
}

那么你也会有一个数据映射器 (BookDataMapper):

public class BookDataMapper {

    private DbAdapter adapter;
    private Set<Book> books;

    public BookDataMapper(DbAdapter adapter) {
        // Assign DbAdapter object to the adapter class member.
    }    

    public void select(long bookId) {
        // 1. Fetch book record from db by bookId and using the injected db adapter.
        // 2. Map fetched db record to a Book object using mapBook().
        // 3. Add Book object to books using addBook().
    }
    public void insert(Book book) {
        // 1. Read the class members of object book.
        // 2. Inject the values in an INSERT SQL statement as parameters.
        // 3. Run the INSERT query and return last insert id.
        // 4. Assign the last insert id to book's ID class member. 
        // 4. Return book.
    }
    public void update(Book book) { ... }
    public void delete(Book book) { ... }

    public void mapBook(array bookRecord){
        // 1. Create a plain Book object.
        // 2. Read bookRecord array and map each field to the corresponding 
        //    class member of the Book object.
        // 3. Return mapped Book object.
    }

    public void addBook(Book book){
        // Add book to books collection.
    }

}

您也可以为数据访问定义更高的抽象层,例如一个BookRepository。你can/should也把里面的藏书(代码)搬过来:

public class BookRepository {

    private BookDataMapper bookMapper;

    public BookRepository(BookDataMapper bookMapper) {
        // Assign BookDataMapper object to the bookMapper class member.
    }    

    public void find(long bookId) {
        // 1. Use bookMapper to fetch book record from the storage by bookId.
        //    Notice that I said storage, not db: per definition, a repository 
        //    hides the details regarding the storage type. The user (client) 
        //    knows only that the book is placed... somewhere.
        // 2. Return the fetched book object.
    }
    public void store(Book book) {
        // 1. Use bookMapper to store the book.
        // 2. Return the book (with last insert id in it).
    }
    public void update(Book book) { ... }
    public void remove(Book book) { ... }

}

最后,定义一个服务(BookBorrowingService)来管理用户想从图书馆借书时的借书过程:

public class BookBorrowingService {

    private UserCardRepository userCardRepository;
    private BookRepository bookRepository;

    public BookBorrowingService(UserCardRepository userCardRepository, BookRepository bookRepository) {
        // 1. Assign UserCardRepository object to the userCardRepository class member.
        // 2. Assign BookRepository object to the bookRepository class member.
    }    

    public void borrowBook(long userCardId, long bookId) {
        // 1. Use userCardRepository and the given card id to find the user card.
        // 2. Validate the card based on its details. If successfull go further.
        //    If not, then return corresonding response to user.
        // 3. Use bookRepository and the given book id to find the book.
        // 4. Return the fetched book object.
    }

}

然后,在主要部分将所有部分绑定在一起:

// Create and share db connection(s).
// Create and share adapter(s).
// Create mappers.
// Create repositories.
BookBorrowingService bookBorrowingService = new BookBorrowingService(userCardRepository, bookRepository);
Book book = borrowBook(123, 4567890);

回答您的问题

以这种方式构建代码的优点是,每个 class 都有很好的分隔责任,符合 Single Responsibility Principle. For example, a domain object's responsibility should only be business logic, not data access. So, in short, this SOLID principle contradicts your first approach. You can also say that a separation of concerns 发生的情况。

使用上述方法的唯一缺点是您必须编写更多代码。

备注:

  • 我不在 Java 中编程。这就是我没有实现更多代码的原因。
  • 使用依赖注入容器。
  • 忘记静态、全局、单例。
  • 使用接口而不是具体实现。

祝你好运!