【问题标题】:Should CRUD methods be part of the object or a service layer? [closed]CRUD 方法应该是对象还是服务层的一部分? [关闭]
【发布时间】:2017-07-23 20:39:18
【问题描述】:

我在学习一些面向对象的设计问题时遇到了这个问题,即图书目录中需要一个 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 对象对我来说看起来相当不错,因为它包含修改/获取有关图书对象的信息以及有关图书的数据所需的所有方法。因此,当使用 OOP 的定义时,这看起来很棒,因为这就是对象应该是的。

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

根据我的经验,第一种方法在理论上看起来很棒,而下一种在实践中很棒。第二种方法是否存在缺陷/陷阱?应该首选哪种方法?

PS:我不确定这些问题是否被接受,如果不是,我很乐意删除/编辑它,但我找不到更好的地方来获得答案:(

【问题讨论】:

  • 我希望Book 上的update() 方法可以更新存储在程序内存中的Book 对象--而不是数据库中的对象我>。除非你为你的类命名,以便清楚地表明它指的是一本书在那个特定的数据库中(或在一些抽象的数据库中),Book 应该独立于它所提供的任何数据库或其他来源from,它的方法不应该对数据库有任何了解。
  • @ajb 感谢您的回复。如果我理解正确,您是说上面的书对象将用于更新书籍/从内存中获取或删除书籍,而不是从数据库中。换句话说,在一个真实的应用程序中,这个逻辑应该在服务类中而不是在书对象中?
  • 我的意思是Book 对象包含有关这本书的信息,而不是包含该书的任何更大的书籍集合的信息,无论是Java Collection、数据库还是一些其他数据结构。因此,任何更新方法都应该只是更新该信息,而不是做任何影响包含结构的事情。对于List&lt;Book&gt;,无论如何都不需要做任何其他事情。但是考虑一个HashMap&lt;Book&gt;,其中书名是关键,然后您更新书名。更新 Book 不会修复 HashMap 现在会出错...
  • ...所以这实际上与“集合”是否在内存中无关;它是关于定义你的课程的目的是什么。抱歉,如果我的第一条评论让您对“记忆”的事情感到困惑。

标签: java oop object-oriented-analysis


【解决方案1】:

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

现在,域对象也称为“模型”。但这可能有点令人困惑。为什么?因为“模型”实际上是一个层。它由三个子层组成:

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

因此,在您的情况下,域对象 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。您还可以/应该在其中移动藏书(代码):

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

回答您的问题

以这种方式构建代码的优点是,每个类都有很好的分隔职责,符合Single Responsibility Principle。例如,域对象的职责应该只是业务逻辑,而不是数据访问。所以,简而言之,这个SOLID 原则与您的第一种方法相矛盾。你也可以说发生了separation of concerns

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

注意事项:

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

祝你好运!

【讨论】:

  • 对于对我的问题投反对票的用户:请让我知道您投反对票的动机,以便我可以相应地更改我的答案。通过这种方式,我们可以共同为本网站的持续改进做出贡献。谢谢。
  • 嘿,谢谢。我不确定是谁投了反对票。
  • @varunkr 不客气。不要担心否决票。无论如何,这对我来说无关紧要,只要它不是用语言来驱动的。
【解决方案2】:

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

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

【讨论】:

  • 感谢您的回复。因此,如果我理解正确,则第二种方法即服务层方法是可行的方法。上述问题中的 Book 对象是我正在阅读的书中的一个对象。我想这本书为了解释 OOP 设计有点简单化,但在实际场景中这不是要走的路。我说的对吗?
  • @varunkr 是的,这是正确的实施方式。这本书可能有点简单,但遵循书籍指南不是问题。但不管怎样,如果你继续编程,你就会学到这些技术。
  • @varunkr 如果我的帖子对您​​有帮助,请将其标记为答案。
猜你喜欢
  • 2013-07-01
  • 2013-06-22
  • 2019-06-03
  • 2021-02-15
  • 1970-01-01
  • 1970-01-01
  • 2022-01-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多