Sử dụng bản đồ thay vì lớp để thể hiện dữ liệu -Rich Hickey


19

Trong video này của Rich Hickey , người tạo Clojure, ông khuyên nên sử dụng bản đồ để thể hiện dữ liệu thay vì sử dụng một lớp để thể hiện nó, như được thực hiện trong Java. Tôi không hiểu làm thế nào nó có thể tốt hơn, vì làm thế nào người dùng API có thể biết các khóa đầu vào là gì nếu chúng được biểu diễn đơn giản dưới dạng bản đồ.

Ví dụ :

PersonAPI {
    Person addPerson(Person obj);
    Map<String, Object> addPerson(Map<String, Object> personMap);
}

Trong chức năng thứ hai, làm thế nào để người dùng API có thể biết đầu vào để tạo ra một người là gì?



Tôi cũng muốn biết điều này và tôi cảm thấy rằng câu hỏi ví dụ không hoàn toàn trả lời nó.
sydan 6/2/2015

Tôi biết tôi đã xem cuộc thảo luận này trước khi ở đâu đó trên SE. Tôi tin rằng đó là trong bối cảnh của JavaScript, nhưng các đối số là như nhau. Không thể tìm thấy nó mặc dù.
Sebastian Redl

2
Vì Clojure là một Lisp, bạn nên làm những việc phù hợp với Lisp. khi bạn sử dụng Java, mã trong ... cũng Java.
AK_

Câu trả lời:


12

Tóm tắt Exagg'itive (TM)

Bạn nhận được một vài điều.

  • Di truyền nguyên mẫu và nhân bản
  • Bổ sung động các tính chất mới
  • Sự tồn tại của các đối tượng của các phiên bản khác nhau (mức đặc tả) của cùng một lớp.
    • Các đối tượng thuộc các phiên bản gần đây hơn (mức đặc tả) sẽ có thêm thuộc tính "tùy chọn".
  • Hướng nội của tài sản, cũ và mới
  • Hướng nội quy tắc xác nhận (thảo luận bên dưới)

Có một nhược điểm chết người.

  • Trình biên dịch không kiểm tra các chuỗi sai chính tả cho bạn.
  • Các công cụ tái cấu trúc tự động sẽ không đổi tên các tên khóa thuộc tính cho bạn - trừ khi bạn trả tiền cho các tên ưa thích.

Vấn đề là, bạn có thể có được sự hướng nội bằng cách sử dụng, ừm, hướng nội. Đây là những gì thường xảy ra:

  • Cho phép phản chiếu.
  • Thêm một thư viện nội tâm lớn vào dự án của bạn.
  • Đánh dấu các phương thức và thuộc tính đối tượng khác nhau bằng các thuộc tính hoặc chú thích.
  • Hãy để thư viện hướng nội làm điều kỳ diệu.

Nói cách khác, nếu bạn không cần phải giao tiếp với FP, bạn không cần phải nghe lời khuyên của Rich Hickey.

Cuối cùng, nhưng không phải là ít nhất (cũng không phải là đẹp nhất), mặc dù sử dụng Stringlàm khóa thuộc tính có ý nghĩa đơn giản nhất, bạn không phải sử dụng Strings. Nhiều hệ thống cũ, bao gồm Android ™, sử dụng ID số nguyên rộng rãi trong toàn bộ khung để tham khảo các lớp, thuộc tính, tài nguyên, v.v.

Android là nhãn hiệu của Google Inc.


Bạn cũng có thể làm cho cả hai thế giới hạnh phúc.

Đối với thế giới Java, hãy triển khai các getters và setters như bình thường.

Đối với thế giới FP, hãy thực hiện

  • Object getPropertyByName(String name)
  • void setPropertyByName(String name, Object value) throws IllegalPropertyChangeException
  • List<String> getPropertyNames()
  • Class<?> getPropertyValueClass(String name)

Bên trong các chức năng này, vâng, mã xấu, nhưng có các plugin IDE sẽ bổ sung cho bạn, sử dụng ... uh, một plugin thông minh đọc mã của bạn.

Phía Java của mọi thứ sẽ vẫn hoạt động như bình thường. Họ sẽ không bao giờ sử dụng phần xấu xí đó của mã. Bạn thậm chí có thể muốn ẩn nó khỏi Javadoc.

Phía FP của thế giới có thể viết bất kỳ mã "leet" nào họ muốn và họ thường không mắng bạn về việc mã bị chậm.


Nói chung, sử dụng bản đồ (túi tài sản) thay cho đối tượng là điều phổ biến trong phát triển phần mềm. Nó không phải là duy nhất cho lập trình chức năng hoặc bất kỳ loại ngôn ngữ cụ thể. Nó có thể không phải là một cách tiếp cận thành ngữ cho bất kỳ ngôn ngữ nào, nhưng có những tình huống đòi hỏi nó.

Cụ thể, tuần tự hóa / giải tuần tự hóa thường đòi hỏi một kỹ thuật tương tự.

Chỉ cần một số suy nghĩ chung về "bản đồ là đối tượng".

  1. Bạn vẫn phải cung cấp một chức năng để xác nhận "bản đồ là đối tượng". Sự khác biệt là "ánh xạ như đối tượng" cho phép các tiêu chí xác nhận linh hoạt hơn (ít hạn chế hơn).
  2. Bạn có thể dễ dàng thêm các trường bổ sung vào "bản đồ dưới dạng đối tượng".
  3. Để cung cấp một đặc tả về yêu cầu tối thiểu của một đối tượng hợp lệ, bạn sẽ cần:
    • Liệt kê bộ khóa "yêu cầu tối thiểu" dự kiến ​​trong bản đồ
    • Đối với mỗi khóa có giá trị cần được xác thực, hãy cung cấp hàm xác thực giá trị
    • Nếu có các quy tắc xác thực cần kiểm tra nhiều giá trị chính, hãy cung cấp điều đó.
    • Lợi ích là gì? Cung cấp đặc tả theo cách này là hướng nội: bạn có thể viết chương trình để truy vấn bộ khóa được yêu cầu tối thiểu và để có được chức năng xác thực cho mỗi khóa.
    • Trong OOP, tất cả những thứ này được cuộn lại thành một hộp đen, với tên "đóng gói". Thay cho logic xác thực có thể đọc bằng máy, người gọi chỉ có thể đọc "tài liệu API" có thể đọc được của con người (nếu may mắn là nó tồn tại).

commonplacecó vẻ hơi mạnh đối với tôi Ý tôi là nó được sử dụng như bạn mô tả, nhưng nó cũng là một trong những thứ khét tiếng / dễ vỡ (như mảng byte hoặc con trỏ trần) mà các thư viện cố gắng giấu đi.
Telastyn 6/2/2015

@Telastyn "Cái đầu xấu xí của một ngàn con rắn" này thường xảy ra trên ranh giới giao tiếp giữa hai hệ thống, vì lý do nào đó, kênh liên lạc hoặc liên quá trình không cho phép các đối tượng được dịch chuyển nguyên vẹn. Tôi đoán các kỹ thuật mới như Bộ đệm giao thức gần như đã loại bỏ trường hợp sử dụng bản đồ cổ xưa này làm đối tượng. Vẫn có thể có các trường hợp sử dụng hợp lệ khác, nhưng tôi có ít kiến ​​thức về điều đó.
rwong

2
Đối với nhược điểm gây tử vong, đồng ý. Nhưng, nếu các tên thuộc tính "dễ viết sai chính tả" và "khó tái cấu trúc" được giữ, càng nhiều càng tốt, trong các hằng hoặc enum , vấn đề đó sẽ biến mất. Tất nhiên, nó giới hạn khả năng mở rộng một số :-(.
user949300 7/2/2015

Nếu "một nhược điểm chết người" thực sự gây tử vong, tại sao một số người có thể sử dụng nó một cách hiệu quả. Ngoài ra, các lớp và gõ tĩnh là trực giao - bạn có thể định nghĩa các lớp trong Clojure, mặc dù nó được gõ động.
Nathan Davis

@NathanDavis (1) Tôi thừa nhận câu trả lời của mình được viết từ góc độ gõ tĩnh (C #) và tôi đã viết câu trả lời này vì tôi có cùng quan điểm của người hỏi. Tôi thừa nhận tôi thiếu quan điểm trung tâm của FP. (2) Chào mừng bạn đến với SE.SE và vì bạn là một nhân vật được kính trọng trong Clojure, xin vui lòng dành thời gian để viết câu trả lời của riêng bạn nếu những câu hỏi hiện tại không thỏa đáng. Downvote trừ đi danh tiếng và câu trả lời mới thu hút upvote mà tăng danh tiếng một cách nhanh chóng. (3) Tôi có thể thấy "các đối tượng không hoàn chỉnh" có thể hữu ích như thế nào - bạn có thể truy vấn 2 thuộc tính cho một đối tượng nhất định (tên, hình đại diện) và bỏ qua phần còn lại.
rwong

9

Đó là một cuộc nói chuyện tuyệt vời của một người thực sự biết những gì anh ấy đang nói. Tôi khuyên độc giả nên xem toàn bộ. Nó chỉ dài 36 phút.

Một trong những điểm chính của anh ấy là sự đơn giản mở ra cơ hội thay đổi sau này. Việc chọn một lớp để đại diện cho một Personlợi ích trước mắt là tạo ra một API có thể xác minh tĩnh, như bạn đã chỉ ra, nhưng điều đó đi kèm với chi phí hạn chế cơ hội hoặc tăng chi phí để thay đổi và tái sử dụng sau này.

Quan điểm của ông là sử dụng lớp học có thể là một lựa chọn hợp lý, nhưng nó nên là một lựa chọn có ý thức đi kèm với nhận thức đầy đủ về chi phí của nó, và các lập trình viên thường làm một công việc rất kém là nhận thấy những chi phí đó, chứ đừng nói đến việc cân nhắc chúng. Sự lựa chọn đó nên được đánh giá lại khi yêu cầu của bạn tăng lên.

Sau đây là một số thay đổi mã (một hoặc hai trong số đó đã được đề cập trong cuộc nói chuyện) có khả năng đơn giản hơn bằng cách sử dụng danh sách bản đồ so với sử dụng danh sách các Personđối tượng:

  • Gửi một người đến một máy chủ REST. (Một hàm được tạo để đưa một Mapnguyên thủy vào định dạng có thể truyền được có khả năng tái sử dụng cao và thậm chí có thể được cung cấp trong thư viện. Một Personđối tượng có thể cần mã tùy chỉnh để thực hiện cùng một công việc).
  • Tự động xây dựng một danh sách những người từ một truy vấn cơ sở dữ liệu quan hệ. (Một lần nữa, một chức năng chung và có thể tái sử dụng cao).
  • Tự động tạo một biểu mẫu để hiển thị và chỉnh sửa một người.
  • Sử dụng các chức năng phổ biến để làm việc với dữ liệu cá nhân rất không đồng nhất, như học sinh so với nhân viên.
  • Lấy danh sách tất cả những người cư trú trong một mã zip nhất định.
  • Sử dụng lại mã đó để có danh sách tất cả các doanh nghiệp trong một mã zip nhất định.
  • Thêm một lĩnh vực dành riêng cho khách hàng cho một người mà không ảnh hưởng đến các khách hàng khác.

Chúng tôi luôn giải quyết các loại vấn đề này và có các mẫu và công cụ cho chúng, nhưng hiếm khi dừng lại để suy nghĩ nếu việc chọn một biểu diễn dữ liệu đơn giản hơn, linh hoạt hơn ngay từ đầu sẽ giúp công việc của chúng tôi dễ dàng hơn.


có phải có một cái tên cho thứ này? Nói, Ánh xạ thuộc tính đối tượng hoặc Ánh xạ thuộc tính đối tượng (cùng dòng với ORM)?
rwong 6/2/2015

4
Choosing a class to represent a Person provides the immediate benefit of creating a statically-verifiable API... but that comes with the cost of limiting opportunities or increasing costs for change and reuse later on.Sai, và vô cùng bất lịch sự. Nó cải thiện cơ hội của bạn để thay đổi sau này, bởi vì khi bạn thực hiện thay đổi đột phá, trình biên dịch sẽ tự động tìm và chỉ ra cho bạn mọi nơi cần được cập nhật để tăng tốc toàn bộ cơ sở mã của bạn. Đó là mã động, nơi bạn không thể làm điều đó, rằng bạn thực sự được kết hợp với các lựa chọn trước đó!
Mason Wheeler

4
@MasonWheeler: Điều bạn thực sự muốn nói là bạn đánh giá cao sự an toàn của kiểu thời gian biên dịch đối với các cấu trúc dữ liệu năng động hơn (và được đánh máy lỏng lẻo hơn).
Robert Harvey

1
Đa hình không phải là một khái niệm giới hạn trong OOP. Trong trường hợp bản đồ, bạn có thể có đa hình bao gồm (nếu các phần tử là kiểu con của một số loại bản đồ có thể xử lý) hoặc đa hình ad-hoc (nếu các phần tử được gắn thẻ hiệp hội). Đây là nội bộ. Các hoạt động có thể được thực hiện trên bản đồ cũng có thể là đa hình. Đa hình tham số khi chúng ta sử dụng hàm bậc cao hơn trên các phần tử hoặc đặc biệt khi gửi đi. Đóng gói có thể đạt được với không gian tên hoặc các hình thức quản lý khả năng hiển thị khác. Về cơ bản, sự cô lập của các đối tượng không bằng việc gán các hoạt động cho các kiểu dữ liệu.
siefca

1
@GillBates tại sao bạn nói vậy? Bạn chỉ mất cơ hội để đưa các phương thức ảo đó vào "Bản đồ" - nhưng đó chính xác là những gì Rich Hickey nói, "ActiveObjects" thực sự là một mô hình chống đối. Bạn nên coi dữ liệu là dữ liệu (dữ liệu) và không đan xen dữ liệu với hành vi. Có những lợi ích đơn giản rất lớn để đạt được bằng cách tách các mối quan tâm.
Virgil

4
  • Nếu dữ liệu có ít hoặc không có hành vi, với nội dung linh hoạt có khả năng thay đổi, hãy sử dụng Bản đồ. IMO, một "javabean" hoặc "Đối tượng dữ liệu" điển hình bao gồm Mô hình miền thiếu máu với N trường, N setters và N getters, là một sự lãng phí thời gian. Đừng cố gắng gây ấn tượng với người khác bằng cấu trúc được tôn vinh của bạn bằng cách gói nó trong một lớp học lạ mắt. Hãy trung thực, làm rõ ý định của bạn và sử dụng Bản đồ. (Hoặc, nếu nó có ý nghĩa với miền của bạn, một đối tượng JSON hoặc XML)

  • Nếu dữ liệu có hành vi thực tế quan trọng, còn gọi là phương thức ( Nói, Đừng hỏi ), thì hãy sử dụng một lớp. Và vỗ nhẹ vào lưng để sử dụng lập trình Hướng đối tượng thực sự :-).

  • Nếu dữ liệu có nhiều hành vi xác nhận thiết yếu và các trường bắt buộc, hãy sử dụng một lớp.

  • Nếu dữ liệu có số lượng hành vi xác nhận vừa phải, đó là đường biên.

  • Nếu dữ liệu kích hoạt các sự kiện thay đổi thuộc tính, điều đó thực sự dễ dàng hơn và ít tẻ nhạt hơn với Bản đồ. Chỉ cần viết một lớp con nhỏ.

  • Một nhược điểm chính của việc sử dụng Bản đồ là người dùng phải truyền các giá trị thành Chuỗi, int, Foos, v.v ... Nếu điều này rất khó chịu và dễ bị lỗi, hãy xem xét một lớp. Hoặc xem xét một lớp người trợ giúp bao bọc Bản đồ với các getters liên quan.


1
Trên thực tế, điều mà Rich Hickey lập luận là nếu dữ liệu có hành vi thực tế quan trọng ... có lẽ bạn đã làm sai toàn bộ "thiết kế". Dữ liệu là "thông tin". Thông tin, trong thế giới thực KHÔNG phải là "nơi lưu trữ dữ liệu". Thông tin không có "hoạt động kiểm soát cách thay đổi thông tin". Chúng tôi không truyền đạt thông tin bằng cách nói cho mọi người biết nơi nó được lưu trữ. Các phép ẩn dụ hướng đối tượng là SOMETIMES một mô hình thích hợp của thế giới ... nhưng thường thì không. Đó là những gì anh ấy nói - "nghĩ về vấn đề ypur". Không phải tất cả mọi thứ là một đối tượng - vài điều là.
Virgil

0

API cho một mapcó hai cấp độ.

  1. API cho bản đồ.
  2. Các quy ước của ứng dụng.

API có thể được mô tả trong bản đồ theo quy ước. Ví dụ, cặp :api api-validatecó thể được đặt trong bản đồ hoặc :api-foo validate-foocó thể là quy ước. Bản đồ thậm chí có thể lưu trữ api api-documentation-link.

Sử dụng các quy ước cho phép lập trình viên tạo một ngôn ngữ cụ thể cho miền nhằm chuẩn hóa quyền truy cập trên các "loại" được triển khai dưới dạng bản đồ. Sử dụng (keys map)cho phép xác định các thuộc tính trong thời gian chạy.

Không có gì kỳ diệu về bản đồ và không có gì kỳ diệu về các vật thể. Đó là tất cả công văn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.