Thiết kế một lớp để lấy toàn bộ các lớp làm tham số thay vì các thuộc tính riêng lẻ


30

Ví dụ, giả sử bạn có một ứng dụng với một lớp được chia sẻ rộng rãi được gọi là User. Lớp này hiển thị tất cả thông tin về người dùng, Id, tên, mức độ truy cập của họ đối với từng mô-đun, múi giờ, v.v.

Dữ liệu người dùng rõ ràng được tham chiếu rộng rãi trên toàn hệ thống, nhưng vì lý do nào, hệ thống được thiết lập để thay vì chuyển đối tượng người dùng này vào các lớp phụ thuộc vào nó, chúng ta chỉ chuyển các thuộc tính riêng lẻ từ nó.

Một lớp yêu cầu id người dùng, đơn giản sẽ yêu cầu GUID userIdlàm tham số, đôi khi chúng ta cũng có thể cần tên người dùng, do đó, nó được truyền vào dưới dạng một tham số riêng. Trong một số trường hợp, điều này được truyền cho các phương thức riêng lẻ, vì vậy các giá trị không được giữ ở cấp độ lớp.

Mỗi lần tôi cần truy cập vào một phần thông tin khác nhau từ lớp Người dùng, tôi phải thay đổi bằng cách thêm tham số và khi thêm quá tải mới là không phù hợp, tôi cũng phải thay đổi mọi tham chiếu đến phương thức hoặc trình xây dựng lớp.

Người dùng chỉ là một ví dụ. Điều này được thực hành rộng rãi trong mã của chúng tôi.

Tôi có đúng không khi nghĩ đây là vi phạm nguyên tắc Mở / Đóng? Không chỉ là hành động thay đổi các lớp hiện có, mà còn thiết lập chúng ở nơi đầu tiên để những thay đổi rộng rãi rất có thể sẽ được yêu cầu trong tương lai?

Nếu chúng ta chỉ truyền vào Userđối tượng, tôi có thể tạo một thay đổi nhỏ cho lớp tôi đang làm việc. Nếu tôi phải thêm một tham số, tôi có thể phải thực hiện hàng tá thay đổi đối với các tham chiếu đến lớp.

Có bất kỳ nguyên tắc khác bị phá vỡ bởi thực hành này? Phụ thuộc đảo ngược có lẽ? Mặc dù chúng tôi không đề cập đến một sự trừu tượng, nhưng chỉ có một loại người dùng, vì vậy không có nhu cầu thực sự để có giao diện Người dùng.

Có những nguyên tắc khác, không RẮN bị vi phạm, chẳng hạn như các nguyên tắc lập trình phòng thủ cơ bản?

Nếu nhà xây dựng của tôi trông như thế này:

MyConstructor(GUID userid, String username)

Hoặc này:

MyConstructor(User theUser)

Đã đăng nó:

Nó đã được đề xuất rằng câu hỏi được trả lời trong "Pass ID hoặc Object?". Điều này không trả lời cho câu hỏi làm thế nào quyết định đi theo một trong hai cách ảnh hưởng đến nỗ lực tuân theo các nguyên tắc RẮN, vốn là cốt lõi của câu hỏi này.


11
@gnat: Đây chắc chắn không phải là một bản sao. Sự trùng lặp có thể là về phương thức xâu chuỗi để đi sâu vào hệ thống phân cấp đối tượng. Câu hỏi này dường như không được hỏi về điều đó cả.
Greg Burghardt

2
Dạng thứ hai thường được sử dụng khi số lượng tham số được truyền đã trở nên khó sử dụng.
Robert Harvey

12
Một điều tôi không thích về chữ ký đầu tiên là không có gì đảm bảo rằng userId và tên người dùng thực sự có nguồn gốc từ cùng một người dùng. Đó là một lỗi tiềm năng có thể tránh được bằng cách đi khắp người dùng khắp nơi. Nhưng quyết định thực sự phụ thuộc vào những gì các phương thức được gọi đang làm với các đối số.
17 của 26

9
Từ ngữ phân tích từ ngữ không có ý nghĩa trong bối cảnh bạn đang sử dụng nó. Thay vào đó, ý của bạn là vượt qua những người khác?
Konrad Rudolph

5
Thế còn Itrong SOLID? MyConstructorvề cơ bản nói bây giờ "Tôi cần một Guidvà một string". Vậy tại sao không có một giao diện cung cấp a Guidvà a string, hãy Userthực hiện giao diện đó và để MyConstructorphụ thuộc vào một thể hiện thực hiện giao diện đó? Và nếu nhu cầu MyConstructorthay đổi, hãy thay đổi giao diện. - Nó giúp tôi rất nhiều khi nghĩ đến các giao diện để "thuộc về" người tiêu dùng hơn là nhà cung cấp . Vì vậy, hãy nghĩ rằng "với tư cách là một người tiêu dùng tôi cần một cái gì đó làm điều này và điều đó" thay vì "như một nhà cung cấp tôi có thể làm điều này và điều đó".
Corak

Câu trả lời:


31

Hoàn toàn không có gì sai khi truyền toàn bộ một Userđối tượng làm tham số. Trong thực tế, nó có thể giúp làm rõ mã của bạn và làm cho các lập trình viên thấy rõ hơn những gì một phương thức thực hiện nếu chữ ký phương thức yêu cầu a User.

Truyền các kiểu dữ liệu đơn giản là tốt, cho đến khi chúng có ý nghĩa khác với những gì chúng là. Xem xét ví dụ này:

public class Foo
{
    public void Bar(int userId)
    {
        // ...
    }
}

Và một ví dụ sử dụng:

var user = blogPostRepository.Find(32);
var foo = new Foo();

foo.Bar(user.Id);

Bạn có thể phát hiện ra khuyết điểm? Trình biên dịch không thể. "Id người dùng" được thông qua chỉ là một số nguyên. Chúng tôi đặt tên cho biến usernhưng khởi tạo giá trị của nó từ blogPostRepositoryđối tượng, có lẽ trả về BlogPostcác đối tượng, không phải Usercác đối tượng - nhưng mã sẽ biên dịch và bạn kết thúc với một lỗi thời gian chạy nhanh.

Bây giờ hãy xem xét ví dụ thay đổi này:

public class Foo
{
    public void Bar(User user)
    {
        // ...
    }
}

Có thể Barphương thức chỉ sử dụng "Id người dùng" nhưng chữ ký phương thức yêu cầu một Userđối tượng. Bây giờ chúng ta hãy quay lại sử dụng ví dụ như trước đây, nhưng sửa đổi nó để vượt qua toàn bộ "người dùng" trong:

var user = blogPostRepository.Find(32);
var foo = new Foo();

foo.Bar(user);

Bây giờ chúng tôi có một lỗi biên dịch. Các blogPostRepository.Findphương thức trả về một BlogPostđối tượng, mà chúng tôi khéo léo gọi là "người sử dụng". Sau đó, chúng tôi chuyển "người dùng" này cho Barphương thức và nhanh chóng gặp lỗi trình biên dịch, bởi vì chúng tôi không thể truyền BlogPostmột phương thức chấp nhận a User.

Hệ thống Kiểu của ngôn ngữ đang được tận dụng để viết mã chính xác nhanh hơn và xác định các lỗi tại thời gian biên dịch, thay vì thời gian chạy.

Thực sự, phải cấu trúc lại rất nhiều mã bởi vì thông tin người dùng thay đổi chỉ là một triệu chứng của các vấn đề khác. Bằng cách chuyển toàn bộ một Userđối tượng, bạn có được các lợi ích ở trên, ngoài các lợi ích của việc không phải cấu trúc lại tất cả các chữ ký phương thức chấp nhận thông tin người dùng khi có gì đó về Userlớp thay đổi.


6
Tôi muốn nói lý do của bạn tự nó thực sự hướng đến các trường đi qua nhưng có các trường là các hàm bao tầm thường xung quanh giá trị thực. Trong ví dụ này, Người dùng có trường loại UserID và UserID có trường có giá trị nguyên duy nhất. Bây giờ, tuyên bố của Bar cho bạn biết ngay lập tức rằng Bar không sử dụng tất cả thông tin về Người dùng, chỉ ID của họ, nhưng bạn vẫn không thể mắc bất kỳ lỗi ngớ ngẩn nào như chuyển một số nguyên không xuất phát từ UserID vào Bar.
Ian

(Tiếp) Tất nhiên kiểu phong cách lập trình này khá tẻ nhạt, đặc biệt là trong ngôn ngữ không có hỗ trợ cú pháp tốt cho nó (ví dụ, Haskell rất phù hợp với phong cách này, vì bạn chỉ có thể khớp với "ID ID người dùng") .
Ian

5
@Ian: Tôi nghĩ rằng việc bọc Id trong giày trượt kiểu riêng của mình xung quanh vấn đề ban đầu do OP đưa ra, đó là thay đổi cấu trúc đối với lớp Người dùng khiến cần phải cấu trúc lại nhiều chữ ký phương thức. Vượt qua toàn bộ đối tượng Người dùng giải quyết vấn đề này.
Greg Burghardt

@Ian: Mặc dù thành thật mà nói, ngay cả khi làm việc trong C #, tôi đã rất muốn bọc Id và sắp xếp trong Struct chỉ để làm rõ hơn một chút.
Greg Burghardt

1
"không có gì sai khi vượt qua một con trỏ xung quanh vị trí của nó." Hoặc một tài liệu tham khảo, để tránh tất cả các vấn đề với con trỏ mà bạn có thể gặp phải.
Yay295

17

Tôi có đúng không khi nghĩ đây là vi phạm nguyên tắc Mở / Đóng?

Không, đó không phải là vi phạm nguyên tắc đó. Nguyên tắc đó liên quan đến việc không thay đổi Usertheo cách ảnh hưởng đến các phần khác của mã sử dụng nó. Những thay đổi của bạn Usercó thể là một sự vi phạm như vậy, nhưng nó không liên quan.

Có bất kỳ nguyên tắc khác bị phá vỡ bởi thực hành này? Phụ thuộc đảo ngược perahaps?

Không. Những gì bạn mô tả - chỉ đưa các phần bắt buộc của đối tượng người dùng vào mỗi phương thức - thì ngược lại: đó là nghịch đảo phụ thuộc thuần túy.

Có những nguyên tắc khác, không RẮN bị vi phạm, chẳng hạn như các nguyên tắc lập trình phòng thủ cơ bản?

Không. Cách tiếp cận này là một cách mã hóa hoàn toàn hợp lệ. Nó không vi phạm các nguyên tắc như vậy.

Nhưng nghịch đảo phụ thuộc chỉ là một nguyên tắc; Đó không phải là một luật không thể phá vỡ. Và DI thuần có thể thêm phức tạp cho hệ thống. Nếu bạn thấy rằng chỉ đưa các giá trị người dùng cần thiết vào các phương thức, thay vì chuyển toàn bộ đối tượng người dùng vào phương thức hoặc hàm tạo, sẽ tạo ra các vấn đề, thì đừng làm theo cách đó. Đó là tất cả về việc có được sự cân bằng giữa các nguyên tắc và chủ nghĩa thực dụng.

Để giải quyết bình luận của bạn:

Có vấn đề với việc phải phân tích một cách không cần thiết một giá trị mới xuống năm cấp của chuỗi và sau đó thay đổi tất cả các tham chiếu cho tất cả năm phương thức hiện có ...

Một phần của vấn đề ở đây là bạn rõ ràng không thích cách tiếp cận này, theo nhận xét "không cần thiết [vượt qua] ...". Và thế là đủ công bằng; Không có câu trả lời đúng ở đây. Nếu bạn thấy nó nặng nề thì đừng làm theo cách đó.

Tuy nhiên, liên quan đến nguyên tắc mở / đóng, nếu bạn tuân thủ nghiêm ngặt thì "... thay đổi tất cả các tham chiếu cho tất cả năm phương thức hiện có ..." là một dấu hiệu cho thấy các phương thức đó đã được sửa đổi, khi chúng nên được sửa đổi đóng cửa để sửa đổi. Trong thực tế, nguyên tắc mở / đóng có ý nghĩa tốt đối với các API công khai, nhưng không có ý nghĩa nhiều đối với các phần bên trong của một ứng dụng.

... nhưng chắc chắn một kế hoạch tuân thủ nguyên tắc đó đến mức có thể thực hiện được sẽ bao gồm các chiến lược để giảm nhu cầu thay đổi trong tương lai?

Nhưng sau đó bạn đi lang thang vào lãnh thổ YAGNI và nó vẫn sẽ trực giao theo nguyên tắc. Nếu bạn có phương thức Foolấy tên người dùng và sau đó bạn cũng muốn Foocó một ngày sinh, theo nguyên tắc, bạn thêm một phương thức mới; Foovẫn không thay đổi. Một lần nữa, đó là cách thực hành tốt cho các API công khai, nhưng nó vô nghĩa đối với mã nội bộ.

Như đã đề cập trước đây, đó là về sự cân bằng và ý thức chung cho bất kỳ tình huống nào. Nếu những thông số đó thường thay đổi, thì có, sử dụng Usertrực tiếp. Nó sẽ cứu bạn khỏi những thay đổi quy mô lớn mà bạn mô tả. Nhưng nếu họ không thường xuyên thay đổi, thì việc chuyển qua chỉ những gì cần thiết cũng là một cách tiếp cận tốt.


Có vấn đề với việc phải phân tích một cách không cần thiết một giá trị mới xuống năm cấp của chuỗi và sau đó thay đổi tất cả các tham chiếu cho tất cả năm phương thức hiện có. Tại sao nguyên tắc Mở / Đóng chỉ áp dụng cho lớp Người dùng và không áp dụng cho lớp tôi hiện đang chỉnh sửa, cũng được các lớp khác sử dụng? Tôi biết rằng nguyên tắc cụ thể là tránh sự thay đổi, nhưng chắc chắn một kế hoạch tuân thủ nguyên tắc đó đến mức có thể thực hiện được sẽ bao gồm các chiến lược để giảm nhu cầu thay đổi trong tương lai?
Jimbo

@Jimbo, tôi đã cập nhật câu trả lời của mình để thử và giải quyết nhận xét của bạn.
David Arno

Tôi đánh giá cao sự đóng góp của bạn. BTW. Ngay cả Robert C Martin cũng không chấp nhận nguyên tắc Mở / Đóng có một quy tắc cứng. Đó là một quy tắc của ngón tay cái chắc chắn sẽ bị phá vỡ. Áp dụng nguyên tắc này là một bài tập trong nỗ lực tuân thủ nó càng nhiều càng tốt. Đó là lý do tại sao tôi sử dụng từ "thực tế" trước đây.
Jimbo

Đây không phải là sự đảo ngược phụ thuộc để truyền các tham số của Người dùng thay vì chính Người dùng.
James Ellis-Jones

@ JamesEllis-Jones, suy luận phụ thuộc lật các phụ thuộc từ "hỏi", sang "nói". Nếu bạn truyền vào một Userthể hiện và sau đó truy vấn đối tượng đó để lấy tham số, thì bạn chỉ đảo ngược một phần các phụ thuộc; vẫn còn một số câu hỏi đang diễn ra Nghịch đảo phụ thuộc thực sự là 100% "nói, đừng hỏi". Nhưng nó đi kèm với một mức giá phức tạp.
David Arno

10

Có, thay đổi chức năng hiện có là vi phạm Nguyên tắc Mở / Đóng. Bạn đang sửa đổi một cái gì đó nên được đóng lại để sửa đổi do yêu cầu thay đổi. Một thiết kế tốt hơn (để không thay đổi khi yêu cầu thay đổi) sẽ được chuyển vào Người dùng cho những thứ nên hoạt động trên người dùng.

Nhưng điều đó có thể đụng chạm tới giao diện Tách riêng Nguyên tắc, vì bạn có thể đi dọc theo con đường nhiều thông tin hơn nhu cầu chức năng để làm công việc của mình.

Vì vậy, như với hầu hết mọi thứ - nó phụ thuộc .

Chỉ sử dụng tên người dùng, hãy để chức năng linh hoạt hơn, hoạt động với tên người dùng bất kể họ đến từ đâu và không cần phải tạo đối tượng Người dùng hoạt động đầy đủ. Nó cung cấp khả năng phục hồi để thay đổi nếu bạn nghĩ rằng nguồn dữ liệu sẽ thay đổi.

Sử dụng toàn bộ Người dùng giúp hiểu rõ hơn về cách sử dụng và tạo hợp đồng chắc chắn hơn với người gọi. Nó cung cấp khả năng phục hồi để thay đổi nếu bạn nghĩ rằng sẽ cần nhiều người dùng hơn.


+1 nhưng tôi không chắc chắn về cụm từ của bạn "bạn có thể chuyển qua nhiều thông tin hơn". Khi bạn vượt qua (Người dùng người dùng), bạn chuyển tối thiểu thông tin, tham chiếu đến một đối tượng. Đúng là tham chiếu có thể được sử dụng để có thêm thông tin, nhưng điều đó có nghĩa là mã gọi không cần phải lấy. Khi bạn vượt qua (GUID userid, chuỗi tên người dùng), phương thức được gọi luôn có thể gọi User.find (userid) để tìm giao diện chung của đối tượng, vì vậy bạn không thực sự che giấu điều gì.
dcorking

5
@dcorking, " Khi bạn vượt qua (Người dùng người dùng), bạn chuyển tối thiểu thông tin, tham chiếu đến một đối tượng ". Bạn truyền thông tin tối đa liên quan đến đối tượng đó: toàn bộ đối tượng. " phương thức được gọi luôn có thể gọi User.find (userid) ...". Trong một hệ thống được thiết kế tốt, điều đó sẽ không thể xảy ra vì phương pháp được đề cập sẽ không có quyền truy cập User.find(). Trong thực tế thậm chí không nên một User.find. Tìm kiếm một người dùng không bao giờ nên là trách nhiệm của User.
David Arno

2
@dcorking - tăng cường. Rằng bạn đang vượt qua một tham chiếu xảy ra nhỏ là sự trùng hợp kỹ thuật. Bạn đang ghép toàn bộ Userchức năng. Có lẽ điều đó có ý nghĩa. Nhưng có lẽ chức năng chỉ nên quan tâm đến tên người dùng - và chuyển qua các thứ như ngày tham gia của người dùng hoặc địa chỉ không chính xác.
Telastyn

@DavidArno có lẽ đó là chìa khóa cho câu trả lời rõ ràng cho OP. Ai có trách nhiệm nên tìm Người dùng? Có một tên cho nguyên tắc thiết kế tách biệt công cụ tìm / nhà máy khỏi lớp không?
vào

1
@dcorking Tôi muốn nói rằng đó là một hàm ý của Nguyên tắc Trách nhiệm duy nhất. Biết nơi Người dùng được lưu trữ và cách truy xuất chúng bằng ID là những trách nhiệm riêng biệt mà một Userlớp không nên có. Có thể có một UserRepositoryhoặc tương tự liên quan đến những điều như vậy.
Hulk

3

Thiết kế này tuân theo Mẫu đối tượng tham số . Nó giải quyết các vấn đề phát sinh từ việc có nhiều tham số trong chữ ký phương thức.

Tôi có đúng không khi nghĩ đây là vi phạm nguyên tắc Mở / Đóng?

Không. Áp dụng mẫu này cho phép nguyên tắc Mở / đóng (OCP). Ví dụ, các lớp phái sinh của Usercó thể được cung cấp dưới dạng tham số tạo ra một hành vi khác nhau trong lớp tiêu thụ.

Có bất kỳ nguyên tắc khác bị phá vỡ bởi thực hành này?

có thể xảy ra. Hãy để tôi giải thích trên cơ sở các nguyên tắc RẮN.

Các nguyên tắc trách nhiệm đơn (SRP) có thể được vi phạm nếu nó có thiết kế như bạn đã giải thích:

Lớp này hiển thị tất cả thông tin về người dùng, Id, tên, mức độ truy cập của họ đối với từng mô-đun, múi giờ, v.v.

Vấn đề là với tất cả các thông tin . Nếu Userlớp có nhiều thuộc tính, nó sẽ trở thành một Đối tượng truyền dữ liệu khổng lồ , vận chuyển thông tin không liên quan từ quan điểm của các lớp tiêu thụ. Ví dụ: Từ quan điểm của một lớp tiêu thụ UserAuthenticationtài sản User.IdUser.Namecó liên quan, nhưng không User.Timezone.

Các phân biệt nguyên tắc Interface (ISP) cũng bị vi phạm với lý do tương tự nhưng thêm góc độ khác. Ví dụ: Giả sử một lớp tiêu thụ UserManagementyêu cầu User.Namephân chia thuộc tính User.LastNameUser.FirstNamelớp UserAuthenticationcũng phải được sửa đổi cho điều này.

May mắn thay, ISP cũng cung cấp cho bạn một cách có thể thoát khỏi vấn đề: Thông thường các Đối tượng tham số hoặc Đối tượng truyền dữ liệu đó bắt đầu nhỏ và phát triển theo thời gian. Nếu điều này trở nên khó sử dụng, hãy xem xét phương pháp sau: Giới thiệu các giao diện phù hợp với nhu cầu của các lớp tiêu thụ. Ví dụ: Giới thiệu các giao diện và để Userlớp xuất phát từ nó:

class User : IUserAuthenticationInfo, IUserLocationInfo { ... }

Mỗi giao diện sẽ hiển thị một tập hợp con các thuộc tính liên quan của Userlớp cần thiết cho một lớp tiêu thụ để hoàn thành hoạt động của nó. Tìm cụm tài sản. Cố gắng sử dụng lại các giao diện. Trong trường hợp của lớp tiêu thụ UserAuthenticationsử dụng IUserAuthenticationInfothay vì User. Sau đó, nếu có thể chia Userlớp thành nhiều lớp cụ thể bằng cách sử dụng các giao diện là "stprint".


1
Khi Người dùng trở nên phức tạp, sẽ có một vụ nổ kết hợp của các giao diện con có thể, ví dụ, nếu Người dùng chỉ có 3 thuộc tính, có 7 kết hợp có thể. Đề xuất của bạn nghe có vẻ tốt nhưng không thể thực hiện được.
dùng949300

1. Phân tích bạn đúng. Tuy nhiên, tùy thuộc vào cách miền được mô hình hóa các bit của thông tin liên quan có xu hướng phân cụm. Vì vậy, thực tế không cần thiết phải đối phó với tất cả các kết hợp giao diện và thuộc tính có thể. 2. Cách tiếp cận được phác thảo không nhằm mục đích trở thành một giải pháp phổ quát, nhưng có lẽ tôi nên thêm một số 'có thể' và 'có thể' vào câu trả lời.
Theo Lenndorff

2

Khi đối mặt với vấn đề này trong mã của riêng tôi, tôi đã kết luận rằng các lớp / đối tượng mô hình cơ bản là câu trả lời.

Một ví dụ phổ biến sẽ là mẫu kho lưu trữ. Thông thường khi truy vấn cơ sở dữ liệu thông qua các kho lưu trữ, nhiều phương thức trong kho lưu trữ có rất nhiều tham số giống nhau.

Quy tắc ngón tay cái của tôi cho các kho lưu trữ là:

  • Khi có nhiều hơn một phương thức có cùng 2 hoặc nhiều tham số, các tham số sẽ được nhóm lại với nhau như một đối tượng mô hình.

  • Trong đó một phương thức lấy nhiều hơn 2 tham số, các tham số sẽ được nhóm lại với nhau như một đối tượng mô hình.

  • Các mô hình có thể kế thừa từ một cơ sở chung, nhưng chỉ khi nó thực sự có ý nghĩa (thường là tốt hơn để tái cấu trúc muộn hơn là bắt đầu với sự kế thừa trong tâm trí).


Các vấn đề với việc sử dụng các mô hình từ các lớp / khu vực khác không trở nên rõ ràng cho đến khi dự án bắt đầu trở nên hơi phức tạp. Chỉ sau đó bạn tìm thấy ít mã hơn sẽ tạo ra nhiều công việc hơn hoặc nhiều sự phức tạp hơn.

Và vâng, hoàn toàn ổn khi có 2 mô hình khác nhau với các thuộc tính giống nhau phục vụ các lớp / mục đích khác nhau (ví dụ: ViewModels vs POCOs).


2

Chúng ta hãy kiểm tra các khía cạnh riêng lẻ của RẮN:

  • Trách nhiệm duy nhất: có thể bị ảnh hưởng nếu mọi người có xu hướng chỉ đi qua các phần của lớp.
  • Mở / đóng: Không liên quan nơi các phần của lớp được truyền xung quanh, chỉ khi toàn bộ đối tượng được truyền xung quanh. (Tôi nghĩ đó là nơi mà sự bất hòa về nhận thức bắt đầu: bạn cần thay đổi mã ở xa nhưng bản thân lớp có vẻ ổn.)
  • Thay thế Liskov: Không thành vấn đề, chúng tôi không thực hiện các lớp con.
  • Nghịch đảo phụ thuộc (phụ thuộc vào trừu tượng, không phải dữ liệu cụ thể). Vâng, điều đó đã vi phạm: Mọi người không có sự trừu tượng, họ lấy ra các yếu tố cụ thể của lớp và vượt qua điều đó. Tôi nghĩ đó là vấn đề chính ở đây.

Một điều có xu hướng nhầm lẫn bản năng thiết kế là lớp học về cơ bản là dành cho các đối tượng toàn cầu và về cơ bản chỉ đọc. Trong tình huống như vậy, vi phạm trừu tượng không gây hại nhiều: Chỉ cần đọc dữ liệu không được sửa đổi sẽ tạo ra một khớp nối khá yếu; chỉ khi nó trở thành một đống lớn, nỗi đau mới trở nên đáng chú ý.
Để khôi phục bản năng thiết kế, chỉ cần giả định rằng đối tượng không phải là toàn cầu. Bối cảnh nào một chức năng sẽ cần nếu Userđối tượng có thể bị đột biến bất cứ lúc nào? Những thành phần nào của đối tượng có khả năng sẽ bị đột biến với nhau? Chúng có thể được tách ra User, cho dù là một tiểu dự án được tham chiếu hoặc như một giao diện chỉ hiển thị một "lát" của các trường liên quan không quan trọng.

Một nguyên tắc khác: Xem xét các hàm sử dụng các phần của Uservà xem trường (thuộc tính) nào có xu hướng đi cùng nhau. Đó là một danh sách sơ bộ tốt của các tiểu dự án - bạn chắc chắn cần phải suy nghĩ liệu chúng có thực sự thuộc về nhau hay không.

Rất nhiều công việc và hơi khó thực hiện và mã của bạn sẽ trở nên kém linh hoạt hơn một chút vì sẽ khó xác định tiểu dự án (giao diện con) cần được truyền đến một chức năng, đặc biệt nếu các tiểu dự án có sự chồng chéo.

Việc tách Userra sẽ thực sự trở nên xấu xí nếu các tiểu dự án trùng nhau, sau đó mọi người sẽ bối rối không biết nên chọn cái nào nếu tất cả các trường bắt buộc là từ chồng lấp. Nếu bạn phân chia theo thứ bậc (ví dụ: bạn có UserMarketSegmenttrong số những thứ khác UserLocation), mọi người sẽ không chắc chức năng họ đang viết ở cấp độ nào: nó xử lý dữ liệu người dùng ở Location cấp độ hay MarketSegmentcấp độ? Chính xác là điều này có thể thay đổi theo thời gian, tức là bạn quay lại thay đổi chữ ký chức năng, đôi khi trên toàn bộ chuỗi cuộc gọi.

Nói cách khác: Trừ khi bạn thực sự biết tên miền của mình và có một ý tưởng khá rõ ràng về mô-đun đang xử lý các khía cạnh nào User, nó không thực sự đáng để cải thiện cấu trúc của chương trình.


1

Đây là một câu hỏi thực sự thú vị. Nó không phụ thuộc.

Nếu bạn nghĩ rằng phương thức của bạn có thể thay đổi trong tương lai trong nội bộ để yêu cầu các tham số khác nhau của đối tượng Người dùng, bạn chắc chắn nên vượt qua toàn bộ. Ưu điểm là mã bên ngoài của phương thức sau đó được bảo vệ khỏi các thay đổi trong phương thức về các tham số mà nó đang sử dụng, như bạn nói sẽ gây ra một loạt các thay đổi bên ngoài. Vì vậy, vượt qua trong toàn bộ người dùng làm tăng đóng gói.

Nếu bạn khá chắc chắn rằng bạn sẽ không bao giờ cần sử dụng bất cứ thứ gì ngoài việc nói email của Người dùng, bạn nên chuyển nó vào. Ưu điểm của việc này là bạn có thể sử dụng phương thức trong phạm vi ngữ cảnh rộng hơn: ví dụ: bạn có thể sử dụng nó với email của Công ty hoặc với email mà ai đó vừa nhập. Điều này làm tăng tính linh hoạt.

Đây là một phần của một loạt các câu hỏi về việc xây dựng các lớp để có phạm vi rộng hay hẹp, bao gồm cả việc có nên tiêm phụ thuộc hay không và có các đối tượng có sẵn trên toàn cầu hay không. Có một xu hướng đáng tiếc tại thời điểm này để nghĩ rằng phạm vi hẹp hơn luôn luôn là tốt. Tuy nhiên, luôn có sự đánh đổi giữa đóng gói và tính linh hoạt như trong trường hợp này.


1

Tôi thấy tốt nhất là truyền càng ít tham số càng tốt và càng nhiều càng cần thiết. Điều này làm cho việc kiểm tra dễ dàng hơn và không yêu cầu đóng gói toàn bộ các đối tượng.

Trong ví dụ của bạn, nếu bạn chỉ sử dụng id người dùng hoặc tên người dùng thì đây là tất cả những gì bạn nên vượt qua. Nếu mẫu này lặp lại nhiều lần và đối tượng người dùng thực tế lớn hơn nhiều thì lời khuyên của tôi là tạo một giao diện nhỏ hơn cho điều đó. Nó có thể là

interface IIdentifieable
{
    Guid ID { get; }
}

hoặc là

interface INameable
{
    string Name { get; }
}

Điều này làm cho việc kiểm tra với chế độ chế nhạo dễ dàng hơn rất nhiều và bạn biết ngay giá trị nào thực sự được sử dụng. Mặt khác, bạn thường cần khởi tạo các đối tượng phức tạp với nhiều phụ thuộc khác mặc dù cuối cùng bạn chỉ cần một hoặc hai thuộc tính.


1

Thỉnh thoảng tôi gặp phải điều gì đó:

  • Một phương thức lấy một đối số kiểu User(hoặc Productbất cứ thứ gì) có nhiều thuộc tính, mặc dù phương thức đó chỉ sử dụng một vài trong số chúng.
  • Vì một số lý do, một số phần của mã cần gọi phương thức đó mặc dù nó không có Userđối tượng được điền đầy đủ . Nó tạo ra một thể hiện và khởi tạo chỉ các thuộc tính mà phương thức thực sự cần.
  • Điều này xảy ra một loạt các lần.
  • Sau một thời gian, khi bạn gặp một phương thức có Userđối số, bạn thấy mình phải tìm các lệnh gọi đến phương thức đó để tìm nơi Userxuất phát để bạn biết thuộc tính nào được điền. Đây có phải là người dùng "thực" có địa chỉ email không, hay nó chỉ được tạo để vượt qua ID người dùng và một số quyền?

Nếu bạn tạo một Uservà chỉ điền vào một vài thuộc tính bởi vì đó là những thuộc tính mà phương thức cần, thì người gọi thực sự biết nhiều hơn về hoạt động bên trong của phương thức so với bình thường.

Thậm chí tệ hơn, khi bạn một ví dụ User, bạn phải biết nó đến từ đâu để bạn biết thuộc tính nào được điền. Bạn không muốn phải biết điều đó.

Theo thời gian, khi các nhà phát triển thấy Userđược sử dụng như một thùng chứa cho các đối số phương thức, họ có thể bắt đầu thêm các thuộc tính vào nó cho các kịch bản sử dụng một lần. Bây giờ nó trở nên xấu đi, bởi vì lớp đang trở nên lộn xộn với các thuộc tính gần như luôn luôn là null hoặc mặc định.

Tham nhũng như vậy là không thể tránh khỏi, nhưng nó xảy ra lặp đi lặp lại khi chúng ta vượt qua một đối tượng xung quanh chỉ vì chúng ta cần truy cập vào một vài thuộc tính của nó. Vùng nguy hiểm là lần đầu tiên bạn thấy ai đó tạo ra một thể hiện Uservà chỉ cần điền vào một vài thuộc tính để họ có thể chuyển nó sang một phương thức. Đặt chân xuống vì nó là một con đường tối.

Nếu có thể, hãy đặt ví dụ đúng cho nhà phát triển tiếp theo bằng cách chỉ chuyển những gì bạn cần vượt qua.

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.