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 中编程。这就是我没有实现更多代码的原因。
- 使用依赖注入容器。
- 忘记静态、全局、单例。
- 使用接口而不是具体实现。
祝你好运!
我正在学习一些面向对象的设计问题,我遇到了一个图书目录中需要 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 中编程。这就是我没有实现更多代码的原因。
- 使用依赖注入容器。
- 忘记静态、全局、单例。
- 使用接口而不是具体实现。
祝你好运!