Tại sao một mô hình miền thiếu máu được coi là xấu trong C # / OOP, nhưng lại rất quan trọng trong F # / FP?


46

Trong một bài đăng trên blog về F # cho vui và lợi nhuận, nó viết:

Trong một thiết kế chức năng, điều rất quan trọng là tách hành vi khỏi dữ liệu. Các kiểu dữ liệu đơn giản và "ngu ngốc". Và sau đó riêng biệt, bạn có một số hàm hoạt động trên các loại dữ liệu đó.

Điều này trái ngược hoàn toàn với thiết kế hướng đối tượng, trong đó hành vi và dữ liệu được kết hợp. Rốt cuộc, đó chính xác là những gì một lớp học. Trong một thiết kế hướng đối tượng thực sự trong thực tế, bạn không nên có gì ngoài hành vi - dữ liệu là riêng tư và chỉ có thể được truy cập thông qua các phương thức.

Trên thực tế, tại OOD, việc không có đủ hành vi xung quanh một loại dữ liệu được coi là Điều xấu và thậm chí còn có một tên: " mô hình miền thiếu máu ".

Cho rằng trong C #, chúng tôi dường như tiếp tục vay mượn từ F # và cố gắng viết mã kiểu chức năng hơn; Tại sao chúng ta không mượn ý tưởng phân tách dữ liệu / hành vi và thậm chí coi nó là xấu? Có phải chỉ đơn giản là định nghĩa không phù hợp với OOP, hoặc có một lý do cụ thể nào đó là xấu trong C # mà vì một lý do nào đó không áp dụng trong F # (và trên thực tế, bị đảo ngược)?

(Lưu ý: Tôi đặc biệt quan tâm đến sự khác biệt trong C # / F # có thể thay đổi quan điểm về điều gì là tốt / xấu, thay vì các cá nhân có thể không đồng ý với ý kiến ​​trong bài đăng trên blog).


1
Chào Dân! Thái độ của bạn là truyền cảm hứng. Bên cạnh nền tảng .NET (và haskell) tôi khuyến khích bạn nhìn vào scala. Debashish Ghosh đã viết một vài blog về mô hình miền với công cụ chức năng, đó là sâu sắc đối với tôi, hy vọng cho bạn quá, here you go: debasishg.blogspot.com/2012/01/...
AndreasScheinert

2
Tôi đã được một đồng nghiệp gửi một bài viết thú vị ngày hôm nay: blog.inf.ed.ac.uk/sapm/2014/02/04/. Có vẻ như mọi người đang bắt đầu thách thức ý tưởng rằng các mô hình miền thiếu máu hoàn toàn xấu; mà tôi nghĩ có thể là một điều tốt!
Daniel Tuppeny

1
Bài đăng trên blog mà bạn tham khảo dựa trên một ý tưởng sai lầm: "Thông thường dữ liệu sẽ bị lộ mà không bị đóng gói. Dữ liệu là bất biến, vì vậy nó không thể bị" làm hỏng "bởi chức năng xử lý sai." Ngay cả các loại bất biến cũng có những bất biến cần được bảo tồn và điều đó đòi hỏi phải ẩn dữ liệu và kiểm soát cách tạo ra nó. Ví dụ: bạn không thể phơi bày việc thực hiện một cây đen đỏ bất biến, bởi vì sau đó ai đó có thể tạo ra một cây chỉ gồm các nút đỏ.
Doval

4
@Doval để công bằng, giống như nói rằng bạn không thể để lộ trình ghi hệ thống tệp vì ai đó có thể lấp đầy đĩa của bạn. Một người nào đó tạo ra một cây chỉ có các nút đỏ hoàn toàn không gây hại cho cây đỏ đen mà họ được nhân bản, cũng như bất kỳ mã nào trong toàn hệ thống đang sử dụng thể hiện được hình thành tốt đó. Nếu bạn viết mã chủ động tạo ra các trường hợp rác mới hoặc thực hiện những điều nguy hiểm, tính bất biến sẽ không cứu bạn, nhưng nó sẽ cứu người khác khỏi bạn . Ẩn thực hiện sẽ không ngăn mọi người viết mã vô nghĩa mà kết thúc chia cho số không.
Jimmy Hoffa

2
@JimmyHoffa Tôi đồng ý, nhưng điều đó không liên quan gì đến những gì tôi chỉ trích. Tác giả tuyên bố rằng thông thường dữ liệu sẽ bị lộ điều đó là bất biến, và tôi đang nói rằng sự bất biến không loại bỏ một cách kỳ diệu nhu cầu che giấu các chi tiết thực hiện.
Doval

Câu trả lời:


37

Lý do chính mà FP nhắm đến điều này và C # OOP không phải là vì trong FP, trọng tâm là sự minh bạch tham chiếu; nghĩa là, dữ liệu đi vào một chức năng và dữ liệu đi ra, nhưng dữ liệu gốc không bị thay đổi.

Trong C # OOP có một khái niệm về ủy thác trách nhiệm trong đó bạn ủy quyền quản lý một đối tượng cho nó và do đó bạn muốn nó thay đổi nội bộ của chính nó.

Trong FP, bạn không bao giờ muốn thay đổi các giá trị trong một đối tượng, do đó, các hàm của bạn được nhúng trong đối tượng của bạn không có ý nghĩa.

Hơn nữa trong FP, bạn có tính đa hình loại cao hơn cho phép các chức năng của bạn được khái quát hơn nhiều so với C # OOP cho phép. Theo cách này, bạn có thể viết một hàm hoạt động cho bất kỳ a, và do đó, nó được nhúng trong một khối dữ liệu không có ý nghĩa gì; mà có thể chặt vài phương pháp này để nó chỉ hoạt động với điều đó đặc biệt loại của a. Hành vi như thế là hoàn toàn tốt và phổ biến trong C # OOP vì bạn không có khả năng trừu tượng hóa dù sao đi nữa, nhưng trong FP thì đó là một sự đánh đổi.

Vấn đề lớn nhất tôi gặp phải trong các mô hình miền thiếu máu trong C # OOP là bạn kết thúc với mã trùng lặp vì bạn có DTO x và 4 chức năng khác nhau thực hiện hoạt động f thành DTO x vì 4 người khác nhau không thấy triển khai khác . Khi bạn đặt phương thức trực tiếp lên DTO x, thì 4 người đó đều thấy việc thực hiện f và sử dụng lại nó.

Các mô hình dữ liệu thiếu máu trong tái sử dụng mã cản trở C # OOP, nhưng đây không phải là trường hợp trong FP vì một hàm duy nhất được khái quát qua rất nhiều loại khác nhau mà bạn có thể sử dụng lại mã lớn hơn vì hàm đó có thể sử dụng được trong nhiều tình huống hơn so với hàm bạn sẽ viết cho một DTO duy nhất trong C #.


Như đã chỉ ra trong các nhận xét , suy luận kiểu là một trong những lợi ích mà FP dựa vào để cho phép tính đa hình đáng kể như vậy, và cụ thể bạn có thể theo dõi điều này trở lại hệ thống loại Hindley Milner với suy luận loại Thuật toán W; suy luận kiểu như vậy trong hệ thống loại C # OOP đã bị tránh vì thời gian biên dịch khi suy luận dựa trên ràng buộc được thêm vào rất lâu do tìm kiếm toàn diện cần thiết, chi tiết tại đây: https://stackoverflow.com/questions/3948834/generics-why -cant-the-Trình biên dịch-infer-the-type-argument-in-this-case


Những tính năng nào trong F # giúp viết loại mã có thể tái sử dụng này dễ dàng hơn? Tại sao mã trong C # không thể tái sử dụng? (Tôi nghĩ rằng tôi đã thấy khả năng có các phương thức lấy các đối số với các thuộc tính cụ thể, mà không cần giao diện; mà tôi đoán sẽ là một khóa chính?)
Danny Tuppeny

7
@DannyTuppeny thành thật F # là một ví dụ kém để so sánh, đó chỉ là một C # mặc quần áo hơi; đó là một ngôn ngữ mệnh lệnh có thể thay đổi giống như C #, nó có một vài cơ sở FP mà C # không có nhưng không nhiều. Hãy nhìn vào haskell để xem nơi FP thực sự nổi bật và những thứ như thế này trở nên khả thi hơn nhiều do các lớp loại cũng như các ADT chung
Jimmy Hoffa

@MattFenwick Tôi đang đề cập rõ ràng đến C # vì điều đó là những gì người đăng đã hỏi về. Nơi tôi đề cập đến OOP trong suốt câu trả lời của tôi ở đây tôi có nghĩa là C # OOP, tôi sẽ chỉnh sửa để làm rõ.
Jimmy Hoffa

2
Một tính năng phổ biến giữa các ngôn ngữ chức năng cho phép loại tái sử dụng này là gõ động hoặc suy ra. Mặc dù bản thân ngôn ngữ có thể sử dụng các loại dữ liệu được xác định rõ, nhưng hàm điển hình không quan tâm dữ liệu là gì, miễn là các hoạt động (các hàm hoặc số học khác) được thực hiện trên chúng là hợp lệ. Điều này cũng có sẵn trong các mô hình OO (ví dụ: Go, có triển khai giao diện ngầm cho phép một đối tượng là Vịt, bởi vì nó có thể Bay, Bơi và Quack, mà không có đối tượng được tuyên bố rõ ràng là Vịt) nhưng nó là khá nhiều điều cần thiết của lập trình chức năng.
KeithS

4
@KeithS Do quá tải dựa trên giá trị trong Haskell Tôi nghĩ bạn có nghĩa là khớp mẫu. Khả năng của Haskell có nhiều hàm cấp cao nhất cùng tên với các mẫu khác nhau ngay lập tức thành 1 hàm toplevel + khớp mẫu.
jozefg

6

Tại sao một mô hình miền thiếu máu được coi là xấu trong C # / OOP, nhưng lại rất quan trọng trong F # / FP?

Câu hỏi của bạn có một vấn đề lớn sẽ giới hạn tiện ích của các câu trả lời bạn nhận được: bạn đang ám chỉ / giả sử rằng F # và FP giống nhau. FP là một họ ngôn ngữ khổng lồ bao gồm viết lại thuật ngữ tượng trưng, ​​động và tĩnh. Ngay cả trong số các ngôn ngữ FP được nhập tĩnh, có nhiều công nghệ khác nhau để thể hiện các mô hình miền như mô-đun bậc cao trong OCaml và SML (không tồn tại trong F #). F # là một trong những ngôn ngữ chức năng này, nhưng nó đặc biệt đáng chú ý vì tinh gọn và đặc biệt, không cung cấp các mô-đun bậc cao hơn hoặc các loại loại cao hơn.

Trên thực tế, tôi không thể bắt đầu cho bạn biết các mô hình miền được thể hiện như thế nào trong FP. Câu trả lời khác ở đây nói rất cụ thể về cách nó được thực hiện trong Haskell và hoàn toàn không áp dụng với Lisp (mẹ của tất cả các ngôn ngữ FP), họ ngôn ngữ ML hoặc bất kỳ ngôn ngữ chức năng nào khác.

Tại sao chúng ta không mượn ý tưởng phân tách dữ liệu / hành vi và thậm chí coi nó là xấu?

Generics có thể được coi là một cách tách dữ liệu và hành vi. Generics đến từ gia đình ML của các ngôn ngữ lập trình chức năng không phải là một phần của OOP. C # có thuốc generic, tất nhiên. Vì vậy, người ta có thể lập luận rằng C # đang dần mượn ý tưởng phân tách dữ liệu và hành vi.

Có phải chỉ đơn giản là định nghĩa không phù hợp với OOP,

Tôi tin rằng OOP dựa trên tiền đề khác nhau cơ bản và do đó, không cung cấp cho bạn các công cụ bạn cần để phân tách dữ liệu và hành vi. Đối với tất cả các mục đích thực tế, bạn cần các kiểu dữ liệu tổng và sản phẩm và gửi qua chúng. Trong ML, điều này có nghĩa là các loại kết hợp và bản ghi và khớp mẫu.

Kiểm tra ví dụ tôi đã đưa ra ở đây .

hoặc có một lý do cụ thể rằng nó xấu trong C # mà vì một lý do nào đó không áp dụng trong F # (và trên thực tế, bị đảo ngược)?

Hãy cẩn thận về việc nhảy từ OOP sang C #. C # không ở đâu gần như thuần túy về OOP như các ngôn ngữ khác. .NET Framework hiện có đầy đủ các khái quát, phương thức tĩnh và thậm chí cả lambdas.

(Lưu ý: Tôi đặc biệt quan tâm đến sự khác biệt trong C # / F # có thể thay đổi quan điểm về điều gì là tốt / xấu, thay vì các cá nhân có thể không đồng ý với ý kiến ​​trong bài đăng trên blog).

Việc thiếu các loại kết hợp và khớp mẫu trong C # làm cho nó gần như không thể thực hiện được. Khi tất cả những gì bạn có là một cái búa, mọi thứ trông giống như một cái đinh ...


2
... khi tất cả những gì bạn có là một cái búa, dân gian OOP sẽ tạo ra các nhà máy sản xuất búa, P +1 để thực sự xác định mấu chốt của những gì C # còn thiếu để cho phép dữ liệu và hành vi được trừu tượng hóa hoàn toàn từ mỗi loại: Kết hợp kiểu và khớp mẫu.
Jimmy Hoffa

-4

Tôi nghĩ rằng trong một ứng dụng kinh doanh, bạn thường không muốn ẩn dữ liệu vì khớp mẫu trên các giá trị bất biến là tuyệt vời để đảm bảo rằng bạn đang bao quát tất cả các trường hợp có thể. Nhưng nếu bạn đang triển khai các thuật toán hoặc cấu trúc dữ liệu phức tạp, tốt hơn bạn nên ẩn chi tiết triển khai biến ADT (kiểu dữ liệu đại số) thành ADT (kiểu dữ liệu trừu tượng).


4
Bạn có thể giải thích thêm một chút về cách áp dụng này cho lập trình hướng đối tượng so với lập trình chức năng không?
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.