MongoDB
 sql >> Cơ Sở Dữ Liệu >  >> NoSQL >> MongoDB

Mẫu thiết kế cho lớp truy cập dữ liệu

Như bạn đã lưu ý, cách tiếp cận phổ biến để lưu trữ dữ liệu trong java là, hoàn toàn không phải là hướng đối tượng. Bản thân điều này không xấu hay tốt:"hướng đối tượng" không phải là ưu điểm cũng không phải nhược điểm, nó chỉ là một trong nhiều mô hình, đôi khi giúp thiết kế kiến ​​trúc tốt (và đôi khi không).

Lý do DAO trong java thường không hướng đối tượng là chính xác những gì bạn muốn đạt được - giảm bớt sự phụ thuộc của bạn vào cơ sở dữ liệu cụ thể. Trong một ngôn ngữ được thiết kế tốt hơn, cho phép đa kế thừa, điều này hoặc tất nhiên, có thể được thực hiện rất thanh lịch theo cách hướng đối tượng, nhưng với java, nó có vẻ rắc rối hơn đáng giá.

Theo nghĩa rộng hơn, cách tiếp cận không phải OO giúp tách dữ liệu cấp ứng dụng của bạn khỏi cách nó được lưu trữ. Đây không chỉ là sự phụ thuộc (không) vào các chi tiết cụ thể của một cơ sở dữ liệu cụ thể mà còn là các lược đồ lưu trữ, điều này đặc biệt quan trọng khi sử dụng cơ sở dữ liệu quan hệ (đừng bắt đầu với ORM):bạn có thể có một lược đồ quan hệ được thiết kế tốt được DAO của bạn dịch liền mạch sang mô hình OO của ứng dụng.

Vì vậy, hầu hết các DAO trong java ngày nay về cơ bản là những gì bạn đã đề cập ở phần đầu - các lớp, đầy đủ các phương thức tĩnh. Một sự khác biệt là, thay vì làm cho tất cả các phương thức tĩnh, tốt hơn là nên có một "phương thức gốc" tĩnh duy nhất (có thể, trong một lớp khác), trả về một thể hiện (singleton) của DAO của bạn, thực hiện một giao diện cụ thể , được sử dụng bởi mã ứng dụng để truy cập cơ sở dữ liệu:

public interface GreatDAO {
    User getUser(int id);
    void saveUser(User u);
}
public class TheGreatestDAO implements GreatDAO {
   protected TheGeatestDAO(){}
   ... 
}
public class GreatDAOFactory {
     private static GreatDAO dao = null;
     protected static synchronized GreatDao setDAO(GreatDAO d) {
         GreatDAO old = dao;
         dao = d;
         return old;
     }
     public static synchronized GreatDAO getDAO() {
         return dao == null ? dao = new TheGreatestDAO() : dao;
     }
}

public class App {
     void setUserName(int id, String name) {
          GreatDAO dao =  GreatDAOFactory.getDao();
          User u = dao.getUser(id);
          u.setName(name);
          dao.saveUser(u);
     }
}

Tại sao lại làm theo cách này trái ngược với các phương thức tĩnh? Chà, nếu bạn quyết định chuyển sang một cơ sở dữ liệu khác thì sao? Đương nhiên, bạn sẽ tạo một lớp DAO mới, triển khai logic cho bộ nhớ mới của bạn. Nếu bạn đang sử dụng các phương thức tĩnh, bây giờ bạn sẽ phải xem qua tất cả mã của mình, truy cập DAO và thay đổi nó để sử dụng lớp mới của bạn, phải không? Đây có thể là một nỗi đau rất lớn. Và nếu sau đó bạn đổi ý và muốn chuyển về db ​​cũ thì sao?

Với cách tiếp cận này, tất cả những gì bạn cần làm là thay đổi GreatDAOFactory.getDAO() và làm cho nó tạo một phiên bản của một lớp khác và tất cả mã ứng dụng của bạn sẽ được sử dụng cơ sở dữ liệu mới mà không có bất kỳ thay đổi nào.

Trong cuộc sống thực, điều này thường được thực hiện mà không có bất kỳ thay đổi nào đối với mã:phương thức gốc lấy tên lớp triển khai thông qua cài đặt thuộc tính và khởi tạo nó bằng cách sử dụng phản chiếu, vì vậy, tất cả những gì bạn cần làm để chuyển đổi triển khai là chỉnh sửa một thuộc tính tập tin. Thực tế có những khuôn khổ - như spring hoặc guice - quản lý cơ chế "tiêm phụ thuộc" này cho bạn, nhưng tôi sẽ không đi vào chi tiết, trước tiên, vì nó thực sự nằm ngoài phạm vi câu hỏi của bạn, và vì tôi không nhất thiết bị thuyết phục rằng lợi ích bạn nhận được từ việc sử dụng những khuôn khổ đó đáng gặp khó khăn khi tích hợp với chúng cho hầu hết các ứng dụng.

Một lợi ích khác (có thể, nhiều khả năng sẽ được tận dụng) của "phương pháp tiếp cận nhà máy" này trái ngược với tĩnh là khả năng kiểm tra. Hãy tưởng tượng rằng bạn đang viết một bài kiểm tra đơn vị, bài kiểm tra đó sẽ kiểm tra tính logic của App của bạn lớp độc lập với bất kỳ DAO bên dưới nào. Bạn không muốn nó sử dụng bất kỳ bộ nhớ cơ bản thực nào vì một số lý do (tốc độ, phải thiết lập nó và dọn dẹp mật ngữ, có thể xảy ra va chạm với các bài kiểm tra khác, khả năng làm ô nhiễm kết quả kiểm tra với các vấn đề trong DAO, không liên quan đến App , mà thực sự đang được thử nghiệm, v.v.).

Để làm điều này, bạn cần một khung thử nghiệm, như Mockito , cho phép bạn "mô phỏng" chức năng của bất kỳ đối tượng hoặc phương thức nào, thay thế nó bằng một đối tượng "giả", với hành vi được xác định trước (Tôi sẽ bỏ qua chi tiết, bởi vì, điều này lại nằm ngoài phạm vi). Vì vậy, bạn có thể tạo đối tượng giả này thay thế DAO của bạn và tạo GreatDAOFactory trả lại hình nộm của bạn thay vì đồ thật bằng cách gọi GreatDAOFactory.setDAO(dao) trước khi kiểm tra (và khôi phục nó sau đó). Nếu bạn đang sử dụng các phương thức tĩnh thay vì lớp cá thể, điều này sẽ không thể thực hiện được.

Một lợi ích nữa, tương tự như việc chuyển đổi cơ sở dữ liệu mà tôi đã mô tả ở trên là "thu hút" dao của bạn với chức năng bổ sung. Giả sử rằng ứng dụng của bạn trở nên chậm hơn khi lượng dữ liệu trong cơ sở dữ liệu tăng lên và bạn quyết định rằng bạn cần một lớp bộ nhớ cache. Triển khai một lớp wrapper, sử dụng cá thể dao thực (được cung cấp cho nó dưới dạng tham số khởi tạo) để truy cập cơ sở dữ liệu và lưu trữ các đối tượng mà nó đọc trong bộ nhớ, để chúng có thể được trả về nhanh hơn. Sau đó, bạn có thể tạo GreatDAOFactory.getDAO của mình khởi tạo trình bao bọc này để ứng dụng tận dụng nó.

(Đây được gọi là "mô hình ủy quyền" ... và có vẻ như đau ở mông, đặc biệt là khi bạn có nhiều phương thức được xác định trong DAO của mình:bạn sẽ phải triển khai tất cả chúng trong trình bao bọc, thậm chí thay đổi hành vi của chỉ một phương thức . Ngoài ra, bạn có thể chỉ cần phân lớp con dao của mình và thêm bộ nhớ đệm vào đó theo cách này. Điều này sẽ đỡ nhàm chán hơn khi viết mã từ trước, nhưng có thể trở thành vấn đề khi bạn quyết định thay đổi cơ sở dữ liệu hoặc tệ hơn là có một tùy chọn chuyển đổi triển khai qua lại.)

Một phương pháp thay thế được sử dụng rộng rãi như nhau (nhưng theo tôi, kém hơn) cho phương pháp "factory" là tạo dao một biến thành viên trong tất cả các lớp cần nó:

public class App {
   GreatDao dao;
   public App(GreatDao d) { dao = d; }
}

Bằng cách này, mã khởi tạo các lớp này cần phải khởi tạo đối tượng dao (vẫn có thể sử dụng nhà máy) và cung cấp nó như một tham số phương thức khởi tạo. Các khung công tác tiêm phụ thuộc mà tôi đã đề cập ở trên, thường làm một cái gì đó tương tự như thế này.

Điều này cung cấp tất cả các lợi ích của phương pháp tiếp cận "phương pháp nhà máy", mà tôi đã mô tả trước đó, nhưng, như tôi đã nói, theo quan điểm của tôi là không tốt. Những bất lợi ở đây là phải viết một hàm tạo cho mỗi lớp ứng dụng của bạn, làm đi làm lại cùng một việc chính xác và cũng không thể khởi tạo các lớp một cách dễ dàng khi cần và một số mất khả năng đọc:với một cơ sở mã đủ lớn , một người đọc mã của bạn, không quen thuộc với nó, sẽ khó hiểu cách triển khai thực tế của dao được sử dụng, cách nó được khởi tạo, cho dù nó là một singleton, một triển khai an toàn theo luồng, cho dù nó giữ trạng thái hay lưu vào bộ nhớ đệm bất cứ điều gì, các quyết định về việc lựa chọn một triển khai cụ thể được thực hiện như thế nào, v.v.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Cập nhật tài liệu con MongoDB khi tài liệu mẹ có thể không tồn tại

  2. Xóa các bản ghi trùng lặp bằng MapReduce

  3. Làm thế nào để giải quyết vấn đề liên quan đến mongoDB một cách hiệu quả?

  4. Node Js Cách tải tệp vào bộ nhớ mà không cần ghi tệp vào hệ thống hoặc không tạo tệp trong thư mục

  5. Sử dụng ObjectId mongo làm id của người dùng có phải là một thực tiễn xấu?