Nguyên thủy vs Class để đại diện cho đối tượng miền đơn giản?


14

Các nguyên tắc chung hoặc quy tắc chung khi nào nên sử dụng một đối tượng tên miền cụ thể so với Chuỗi hoặc số đơn giản?

Ví dụ:

  • Lớp tuổi vs Số nguyên?
  • Lớp FirstName vs String?
  • UniqueID vs String
  • Lớp PhoneNumber vs String vs Long?
  • Lớp DomainName vs String?

Tôi nghĩ rằng hầu hết các học viên OOP chắc chắn sẽ nói các lớp cụ thể cho PhoneNumber và DomainName. Càng nhiều quy tắc xung quanh những gì làm cho chúng hợp lệ và cách so sánh chúng làm cho các lớp đơn giản trở nên dễ dàng và an toàn hơn để giải quyết. Nhưng đối với ba người đầu tiên có nhiều tranh luận.

Tôi chưa bao giờ bắt gặp một lớp "Thời đại" nhưng người ta có thể lập luận rằng nó có nghĩa là nó không âm (được thôi tôi biết bạn có thể tranh luận về độ tuổi tiêu cực nhưng đó là một ví dụ tốt rằng nó gần như tương đương với một số nguyên nguyên thủy).

Chuỗi là phổ biến để đại diện cho "Tên" nhưng nó không hoàn hảo vì Chuỗi trống là Chuỗi hợp lệ nhưng không phải là tên hợp lệ. So sánh thường sẽ được thực hiện bỏ qua trường hợp. Chắc chắn có các phương pháp để kiểm tra trống, thực hiện so sánh không phân biệt chữ hoa chữ thường, v.v ... nhưng nó đòi hỏi người tiêu dùng phải làm điều này.

Câu trả lời có phụ thuộc vào môi trường không? Tôi chủ yếu quan tâm đến doanh nghiệp / phần mềm giá trị cao sẽ tồn tại và được duy trì trong hơn một thập kỷ.

Có lẽ tôi đang xem xét lại điều này nhưng tôi thực sự muốn biết liệu có ai có quy tắc khi nào nên chọn lớp so với nguyên thủy không.


2
Lớp tuổi không phải là một ý tưởng tốt nếu nó có thể được thay thế bằng một số nguyên. Nếu nó lấy một ngày sinh trong hàm tạo và có các phương thức getCienAge () và getAgeAt (Date date) thì lớp có ý nghĩa. Nhưng nó không thể được thay thế bằng một số nguyên.
kiwiron

5
"Một chuỗi đôi khi không phải là một chuỗi. Mô hình hóa nó phù hợp.". Có một bài viết hay của Mark Seemann về 'nỗi ám ảnh nguyên thủy': blog.ploeh.dk/2015/01/19/ mẹo
Serhii Shushliapin

4
Trên thực tế, một lớp Age thực sự có ý nghĩa theo một cách kỳ lạ. Định nghĩa phổ biến của phương Tây về Tuổi là 0 khi sinh và thêm 1 vào ngày sinh nhật tiếp theo của bạn. Phương pháp Đông Á là bạn 1 tuổi khi sinh và 1 được thêm vào ngày đầu năm mới. Do đó tùy thuộc vào địa phương của bạn, tuổi của bạn có thể được tính khác nhau. EG từ en.wikipedia.org/wiki/East_Asian_age_reckaming people may be one or two years older in Asian reckoning than in the western age system
Peter M

@PeterM, đó là thông tin tốt! Ngày / Thời đại và bây giờ tuổi. Chúng nên là những khái niệm đơn giản, nhưng điều này chứng tỏ có nhiều điều phức tạp trên thế giới hơn những gì chúng ta sử dụng để xử lý.
Machado

6
Tôi thấy rất nhiều bài viết bên dưới câu hỏi này, nhưng câu trả lời không thực sự phức tạp. Bạn sử dụng một Agelớp thay vì một số nguyên khi bạn cần hành vi bổ sung mà một lớp như vậy sẽ cung cấp.
Robert Harvey

Câu trả lời:


19

Các nguyên tắc chung hoặc quy tắc chung khi nào nên sử dụng một đối tượng tên miền cụ thể so với Chuỗi hoặc số đơn giản?

Nguyên tắc chung là bạn muốn mô hình hóa tên miền của mình bằng ngôn ngữ cụ thể của tên miền.

Hãy xem xét: tại sao chúng ta sử dụng số nguyên? Chúng ta có thể biểu diễn tất cả các số nguyên với các chuỗi dễ dàng như vậy. Hoặc với byte.

Nếu chúng tôi đang lập trình bằng ngôn ngữ bất khả tri miền bao gồm các kiểu nguyên thủy cho số nguyên tuổi, bạn sẽ chọn loại nào?

Những gì nó thực sự đi xuống là bản chất "nguyên thủy" của một số loại nhất định là một tai nạn của sự lựa chọn ngôn ngữ để thực hiện của chúng tôi.

Số, đặc biệt, thường yêu cầu bối cảnh bổ sung. Tuổi không chỉ là một con số mà nó còn có thứ nguyên (thời gian), đơn vị (năm?), Quy tắc làm tròn! Thêm tuổi với nhau có ý nghĩa theo cách thêm tuổi vào tiền không.

Làm cho các loại khác biệt cho phép chúng tôi mô hình sự khác biệt giữa một địa chỉ email chưa được xác minh và địa chỉ email được xác minh .

Tai nạn về cách các giá trị này được thể hiện trong bộ nhớ là một trong những phần ít thú vị nhất. Mô hình miền không quan tâm đến a CustomerIdlà int, hoặc String hoặc UUID / GUID hoặc nút JSON. Nó chỉ muốn các chi phí.

Chúng ta có thực sự quan tâm liệu số nguyên là endian lớn hay endian nhỏ? Chúng ta có quan tâm nếu Listchúng ta đã được thông qua là một sự trừu tượng trên một mảng, hoặc một biểu đồ? Khi chúng ta phát hiện ra rằng số học chính xác kép là không hiệu quả và chúng ta cần thay đổi thành biểu diễn dấu phẩy động, mô hình miền có quan tâm không?

Parnas, năm 1972 , đã viết

Thay vào đó, chúng tôi đề xuất rằng người ta bắt đầu với một danh sách các quyết định thiết kế khó khăn hoặc quyết định thiết kế có khả năng thay đổi. Mỗi mô-đun sau đó được thiết kế để che giấu quyết định như vậy từ những người khác.

Theo một nghĩa nào đó, các loại giá trị cụ thể của miền mà chúng tôi giới thiệu là các mô-đun cô lập quyết định của chúng tôi về việc nên sử dụng biểu diễn cơ bản nào của dữ liệu.

Vì vậy, ưu điểm là tính mô đun - chúng tôi có được một thiết kế nơi dễ quản lý phạm vi thay đổi hơn. Nhược điểm là chi phí - việc tạo ra các loại bespoke mà bạn cần nhiều hơn, việc chọn đúng loại đòi hỏi phải hiểu sâu hơn về tên miền. Lượng công việc cần thiết để tạo mô-đun giá trị sẽ phụ thuộc vào phương ngữ Blub cục bộ của bạn .

Các thuật ngữ khác trong phương trình có thể bao gồm thời gian dự kiến ​​của giải pháp (mô hình cẩn thận cho kho kịch bản sẽ được chạy một lần có lợi tức đầu tư tệ hại), mức độ gần với năng lực cốt lõi của doanh nghiệp.

Một trường hợp đặc biệt mà chúng ta có thể xem xét là liên lạc qua một ranh giới . Chúng tôi không muốn ở trong tình huống thay đổi đối với một đơn vị có thể triển khai được yêu cầu thay đổi phối hợp với các đơn vị có thể triển khai khác. Vì vậy, các thông điệp có xu hướng tập trung nhiều hơn vào các đại diện, mà không xem xét các bất biến hoặc các hành vi cụ thể của miền. Chúng tôi sẽ không cố gắng truyền đạt "giá trị này phải hoàn toàn tích cực" ở định dạng tin nhắn, mà là truyền đạt đại diện của nó trên dây và áp dụng xác thực cho đại diện đó ở ranh giới miền.


Điểm tuyệt vời về việc che giấu (hoặc trừu tượng hóa) những điều khó khăn và có khả năng thay đổi.
user949300

4

Bạn đang nghĩ về sự trừu tượng, và điều đó thật tuyệt. Tuy nhiên, những thứ mà danh sách của bạn đối với tôi dường như chủ yếu là các thuộc tính riêng lẻ của một thứ gì đó lớn hơn (tất nhiên không phải tất cả cùng một thứ).

Điều tôi cảm thấy quan trọng hơn là cung cấp sự trừu tượng hóa các nhóm thuộc tính với nhau và cung cấp cho chúng một ngôi nhà thích hợp, chẳng hạn như lớp Person, để lập trình viên máy khách không phải xử lý nhiều thuộc tính, mà là một mức độ trừu tượng cao hơn.

Khi bạn có sự trừu tượng này, bạn không nhất thiết cần phải trừu tượng hóa thêm cho các thuộc tính chỉ là giá trị. Lớp Person có thể giữ Ngày sinh dưới dạng Ngày (nguyên thủy), cùng với PhoneNumbers là danh sách các chuỗi (nguyên thủy) và Tên dưới dạng chuỗi (nguyên thủy).

Nếu bạn có một số điện thoại có mã định dạng, thì bây giờ đó là hai thuộc tính và sẽ xứng đáng là một lớp cho số điện thoại (vì nó cũng có thể có một số hành vi, chẳng hạn như chỉ nhận được số so với nhận được số điện thoại được định dạng).

Nếu bạn có Tên là nhiều thuộc tính (ví dụ: tiền tố / tiêu đề, đầu tiên, cuối cùng, hậu tố) sẽ xứng đáng với một lớp (như bây giờ bạn có một số hành vi như lấy tên đầy đủ dưới dạng chuỗi so với nhận các phần nhỏ hơn, tiêu đề, v.v. .).


1

Bạn nên lập mô hình khi bạn cần, nếu bạn chỉ muốn một số dữ liệu, bạn có thể sử dụng các kiểu nguyên thủy nhưng nếu bạn muốn thực hiện nhiều thao tác hơn trên các dữ liệu này, thì nên được mô hình hóa trong các lớp cụ thể, ví dụ (tôi đồng ý với @kiwiron trong nhận xét của anh ấy) Lớp tuổi không có ý nghĩa, nhưng sẽ tốt hơn nếu thay thế nó bằng lớp Sinh nhật và bạn đặt các phương thức này ở đó.

Ví dụ, lợi ích của việc mô hình hóa các khái niệm này trong các lớp là bạn thực thi sự phong phú của mô hình của mình, thay vào đó, bạn có một phương pháp chấp nhận bất kỳ Ngày nào, người dùng có thể điền vào bất kỳ ngày nào, ngày bắt đầu WW II hoặc ngày kết thúc Chiến tranh Lạnh, trong những ngày này vẫn là ngày sinh hợp lệ. bạn có thể thay thế nó bằng Ngày sinh để trong trường hợp này, bạn thực thi phương pháp của mình để chấp nhận Ngày sinh không chấp nhận bất kỳ Ngày nào.

Đối với Firstname tôi không thấy nhiều lợi ích, UniqueID cũng vậy, PhoneNumber một chút, bạn có thể yêu cầu nó cung cấp cho bạn quốc gia và khu vực chẳng hạn vì nói chung PhoneNumber đề cập đến các quốc gia và khu vực, một số nhà khai thác sử dụng số Suffixes được xác định trước để phân loại Di động , Office ... số, vì vậy bạn có thể thêm các phương thức này vào lớp đó, tương tự cho Tên miền.

Để biết thêm chi tiết, tôi khuyên bạn nên xem qua Đối tượng giá trị trong DDD (Thiết kế hướng tên miền).


1

Những gì nó phụ thuộc vào là bạn có hành vi (mà bạn cần duy trì và / hoặc thay đổi) liên quan đến dữ liệu đó hay không.

Nói tóm lại, nếu đó chỉ là một phần dữ liệu bị đảo lộn mà không có nhiều hành vi liên quan đến nó, hãy sử dụng kiểu nguyên thủy hoặc, đối với các phần dữ liệu phức tạp hơn một chút, cấu trúc dữ liệu (phần lớn không có hành vi) (ví dụ: một lớp chỉ có getters và setters).

Mặt khác, nếu bạn thấy rằng, để đưa ra quyết định, bạn đang kiểm tra hoặc thao tác dữ liệu này ở nhiều nơi trong mã của bạn, các vị trí thường không gần nhau - sau đó biến nó thành một đối tượng thích hợp bằng cách xác định một lớp và đặt các hành vi có liên quan bên trong nó như là các phương pháp. Nếu vì một lý do nào đó mà bạn cảm thấy không hợp lý khi định nghĩa một lớp như vậy, thì một cách khác là hợp nhất hành vi này thành một loại lớp "dịch vụ" hoạt động trên dữ liệu này.


1

Các ví dụ của bạn trông rất giống các thuộc tính hoặc thuộc tính của một thứ khác. Điều đó có nghĩa là nó sẽ hoàn toàn hợp lý khi bắt đầu chỉ với người nguyên thủy. Tại một số điểm bạn sẽ cần phải sử dụng một nguyên thủy, vì vậy tôi thường tiết kiệm sự phức tạp khi tôi thực sự cần nó.

Ví dụ: giả sử tôi đang tạo một trò chơi và tôi không phải lo lắng về các nhân vật già. Sử dụng một số nguyên cho điều đó có ý nghĩa hoàn hảo. Độ tuổi có thể được sử dụng trong các kiểm tra đơn giản để xem liệu nhân vật có được phép làm điều gì đó hay không.

Như những người khác đã chỉ ra, tuổi tác có thể là một chủ đề phức tạp tùy thuộc vào địa phương của bạn. Nếu bạn có nhu cầu điều hòa các lứa tuổi ở các địa phương khác nhau, v.v. thì việc giới thiệu một Agelớp có DateOfBirthLocalenhư các thuộc tính là hoàn hảo . Lớp Tuổi đó cũng có thể tính tuổi hiện tại ở bất kỳ dấu thời gian nào.

Vì vậy, có những lý do để thúc đẩy một nguyên thủy cho một lớp, nhưng chúng rất cụ thể ứng dụng. Dưới đây là một số ví dụ:

  • Bạn có logic xác thực đặc biệt phải được áp dụng ở mọi nơi loại thông tin được sử dụng (ví dụ: số điện thoại, địa chỉ email).
  • Bạn có logic định dạng đặc biệt được sử dụng để kết xuất cho con người đọc (ví dụ: địa chỉ IP4 và IP6)
  • Bạn có một tập hợp các hàm liên quan đến loại đối tượng đó
  • Bạn có các quy tắc đặc biệt để so sánh các loại giá trị tương tự

Về cơ bản, nếu bạn có một loạt các quy tắc về cách xử lý một giá trị mà bạn muốn được sử dụng nhất quán trong suốt dự án của mình, hãy tạo một lớp. Nếu bạn có thể sống với các giá trị đơn giản vì nó không thực sự quan trọng đối với chức năng, hãy sử dụng nguyên thủy.


1

Vào cuối ngày, một đối tượng chỉ là một nhân chứng rằng một số quy trình thực sự có ran .

Một nguyên thủy intcó nghĩa là một số quá trình đã loại bỏ 4 byte bộ nhớ và sau đó lật các bit cần thiết để biểu diễn một số nguyên trong phạm vi từ -2,147,483,648 đến 2,147,483,647; đó không phải là một tuyên bố mạnh mẽ về khái niệm tuổi tác. Tương tự, một (không phải null) System.Stringcó nghĩa là một số byte được phân bổ và các bit được lật để thể hiện một chuỗi các ký tự Unicode đối với một số mã hóa.

Vì vậy, khi quyết định cách thể hiện đối tượng miền của bạn, bạn nên suy nghĩ về quy trình mà nó phục vụ như một nhân chứng. Nếu một chuỗi FirstNamethực sự phải là một chuỗi không trống, thì nó sẽ là một nhân chứng cho thấy hệ thống đã chạy một số quy trình đảm bảo rằng ít nhất một ký tự không phải khoảng trắng được tạo ra trong một chuỗi các ký tự được trao cho bạn.

Tương tự như vậy, một Ageđối tượng có thể là một nhân chứng cho thấy một số quá trình đã tính toán sự khác biệt giữa hai ngày. Vì intsnhìn chung không phải là kết quả của việc tính toán sự khác biệt giữa hai ngày, nên chúng không đủ để thể hiện độ tuổi.

Vì vậy, điều khá rõ ràng là bạn hầu như luôn muốn tạo một lớp (chỉ là một chương trình) cho mỗi đối tượng miền. Vậy vấn đề là gì? Vấn đề là C # (mà tôi yêu thích) và Java đòi hỏi quá nhiều nghi thức trong việc tạo các lớp. Nhưng, chúng ta có thể tưởng tượng các cú pháp thay thế sẽ giúp việc xác định các đối tượng miền đơn giản hơn nhiều:

//hypothetical syntax
class Age = |start:date - end:date|.Years

(* ML-like syntax *)
type Age(x, y) = datediff(year, x, y)

//C#-like syntax with primary constructors and expression-bodied classes
class Age(DateTime x, DateTime y) => implicit operator int (Age a) => Abs((y - x).Years);

Ví dụ, F # thực sự có một cú pháp hay cho việc này ở dạng Mẫu hoạt động :

//Impossible to produce an `Age` without first computing the difference of two dates
//But, any pair (tuple) of dates is implicitly converted to an `Age` when needed.

let (|Age|) (x, y) =  (date_diff y x).Days / 365

Vấn đề là chỉ vì ngôn ngữ lập trình của bạn khiến việc xác định các đối tượng miền không có nghĩa là nó thực sự là một ý tưởng tồi để làm điều đó.


Chúng tôi cũng gọi những điều này là đăng điều kiện , nhưng tôi thích thuật ngữ "nhân chứng" hơn vì nó là bằng chứng cho thấy một số quy trình có thể và đã chạy.


0

Hãy suy nghĩ về những gì bạn nhận được từ việc sử dụng một lớp so với một nguyên thủy. Nếu sử dụng một lớp học có thể giúp bạn

  • Thẩm định
  • định dạng
  • phương pháp hữu ích khác
  • nhập an toàn (ví dụ: với một PhoneNumberlớp, tôi không thể gán nó cho một chuỗi hoặc một chuỗi ngẫu nhiên (ví dụ: "dưa chuột") trong đó tôi mong đợi một số điện thoại)
  • bất kỳ lợi ích khác

và những lợi ích đó làm cho cuộc sống của bạn dễ dàng hơn, cải thiện chất lượng mã, dễ đọc vượt qua nỗi đau của việc sử dụng những thứ đó, hãy làm điều đó. Nếu không, hãy gắn bó với các nguyên thủy. Nếu nó gần, hãy thực hiện một cuộc gọi phán xét.

Cũng nhận ra rằng đôi khi việc đặt tên tốt chỉ có thể xử lý nó (ví dụ: một chuỗi có tên firstNameso với tạo cả một lớp chỉ bao bọc một thuộc tính chuỗi). Các lớp học không phải là cách duy nhất để giải quyết mọi thứ.

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.