Truyền một đối tượng vào một phương thức làm thay đổi đối tượng, đó có phải là một mẫu (chống) phổ biến không?


17

Tôi đang đọc về mùi mã phổ biến trong cuốn sách Tái cấu trúc của Martin Fowler . Trong bối cảnh đó, tôi đã tự hỏi về một mẫu mà tôi đang thấy trong một cơ sở mã, và người ta có thể coi đó là một mẫu khách quan.

Mẫu là một trong đó một đối tượng được truyền dưới dạng đối số cho một hoặc nhiều phương thức, tất cả đều thay đổi trạng thái của đối tượng, nhưng không có đối tượng nào trả về đối tượng. Vì vậy, nó dựa vào việc vượt qua bởi tính chất tham chiếu của (trong trường hợp này) C # /. NET.

var something = new Thing();
// ...
Foo(something);
int result = Bar(something, 42);
Baz(something);

Tôi thấy rằng (đặc biệt là khi các phương thức không được đặt tên thích hợp) Tôi cần xem xét các phương thức đó để hiểu nếu trạng thái của đối tượng đã thay đổi. Nó làm cho việc hiểu mã trở nên phức tạp hơn, vì tôi cần theo dõi nhiều cấp độ của ngăn xếp cuộc gọi.

Tôi muốn đề xuất cải thiện mã đó để trả về một đối tượng (nhân bản) khác với trạng thái mới hoặc bất cứ điều gì cần thiết để thay đổi đối tượng tại trang web cuộc gọi.

var something1 =  new Thing();
// ...

// Let's return a new instance of Thing
var something2 = Foo(something1);

// Let's use out param to 'return' other info about the operation
int result;
var something3 = Bar(something2, out result);

// If necessary, let's capture and make explicit complex changes
var changes = Baz(something3)
something3.Apply(changes);

Đối với tôi, dường như mẫu đầu tiên được chọn dựa trên các giả định

  • rằng nó là công việc ít hơn, hoặc yêu cầu ít dòng mã hơn
  • rằng nó cho phép chúng ta vừa thay đổi đối tượng, vừa trả lại một số thông tin khác
  • rằng nó hiệu quả hơn vì chúng ta có ít trường hợp hơn Thing.

Tôi minh họa một giải pháp thay thế, nhưng để đề xuất nó, người ta cần có lập luận chống lại giải pháp ban đầu. Điều gì, nếu có, các đối số có thể được đưa ra để làm cho trường hợp rằng giải pháp ban đầu là một mô hình chống?

Và những gì, nếu có bất cứ điều gì là sai với giải pháp thay thế của tôi?


1
Đây là một hiệu ứng phụ
Dave Hillier

1
@DaveHillier Cảm ơn, tôi đã quen với thuật ngữ này, nhưng đã không thực hiện kết nối.
Michiel van Oosterhout

Câu trả lời:


9

Có, giải pháp ban đầu là một mô hình chống đối với các lý do bạn mô tả: làm cho nó khó lý luận về những gì đang xảy ra, đối tượng không chịu trách nhiệm cho trạng thái / việc thực hiện của chính nó (phá vỡ đóng gói). Tôi cũng sẽ nói thêm rằng tất cả những thay đổi trạng thái đó là các hợp đồng ngầm định của phương thức, làm cho phương thức đó trở nên mong manh khi đối mặt với các yêu cầu thay đổi.

Điều đó nói rằng, giải pháp của bạn có một số nhược điểm riêng, trong đó rõ ràng nhất là các đối tượng nhân bản không tuyệt vời. Nó có thể chậm cho các đối tượng lớn. Nó có thể dẫn đến các lỗi trong đó các phần khác của mã giữ các tham chiếu cũ (có khả năng là trường hợp trong cơ sở mã mà bạn mô tả). Làm cho những đối tượng rõ ràng không thể giải quyết được ít nhất một vài trong số những vấn đề này, nhưng là một sự thay đổi mạnh mẽ hơn.

Trừ khi các đối tượng là nhỏ và hơi nhất thời (điều này làm cho chúng trở thành ứng cử viên tốt cho tính bất biến), tôi sẽ có xu hướng chỉ đơn giản là di chuyển nhiều hơn trạng thái chuyển đổi vào chính các đối tượng. Điều đó cho phép bạn ẩn chi tiết triển khai của các chuyển đổi này và đặt ra các yêu cầu mạnh mẽ hơn xung quanh ai / cái gì / nơi những chuyển đổi trạng thái đó xảy ra.


1
Ví dụ: nếu tôi có một đối tượng "Tệp", tôi sẽ không cố gắng di chuyển bất kỳ phương thức thay đổi trạng thái nào vào đối tượng đó - điều đó sẽ vi phạm SRP. Điều đó vẫn hợp lệ ngay cả khi bạn có các lớp riêng thay vì lớp thư viện như "Tệp" - việc đưa mọi logic chuyển trạng thái vào lớp của đối tượng không thực sự có ý nghĩa.
Doc Brown

@Tetastyn Tôi biết đây là câu trả lời cũ, nhưng tôi gặp khó khăn khi hình dung đề xuất của bạn trong đoạn cuối bằng các thuật ngữ cụ thể. Bạn có thể xây dựng hoặc đưa ra một ví dụ?
AaronLS

@AaronLS - Thay vì Bar(something)(và sửa đổi trạng thái something), hãy tạo Barthành viên của somethingloại. something.Bar(42)có nhiều khả năng đột biến something, đồng thời cho phép bạn sử dụng các công cụ OO (trạng thái riêng tư, giao diện, v.v.) để bảo vệ somethingtrạng thái của mình
Telastyn

14

khi các phương thức không được đặt tên thích hợp

Trên thực tế, đó là mùi mã thực sự. Nếu bạn có một đối tượng có thể thay đổi, nó cung cấp các phương thức để thay đổi trạng thái của nó. Nếu bạn có một cuộc gọi đến một phương thức như vậy được nhúng trong một nhiệm vụ của một số câu lệnh khác, thì có thể cấu trúc lại nhiệm vụ đó thành một phương thức của chính nó - điều này khiến bạn rơi vào tình huống được mô tả chính xác. Nhưng nếu bạn không có tên phương thức như FooBar, nhưng phương thức làm rõ rằng chúng thay đổi đối tượng, tôi không thấy vấn đề ở đây. Hãy nghĩ về

void AddMessageToLog(Logger logger, string msg)
{
    //...
}

hoặc là

void StripInvalidCharsFromName(Person p)
{
// ...
}

hoặc là

void AddValueToRepo(Repository repo,int val)
{
// ...
}

hoặc là

void TransferMoneyBetweenAccounts(Account source, Account destination, decimal amount)
{
// ...
}

hoặc một cái gì đó tương tự - Tôi không thấy bất kỳ lý do nào ở đây để trả về một đối tượng được nhân bản cho các phương thức đó và cũng không có lý do nào để xem xét việc thực hiện của chúng để hiểu rằng chúng sẽ thay đổi trạng thái của đối tượng được thông qua.

Nếu bạn không muốn tác dụng phụ, hãy làm cho các đối tượng của bạn không thay đổi, nó sẽ thực thi các phương thức như các phương pháp trên để trả về một đối tượng đã thay đổi (nhân bản) mà không thay đổi đối tượng ban đầu.


Bạn đã đúng, tái cấu trúc phương thức đổi tên có thể cải thiện tình hình bằng cách làm rõ tác dụng phụ. Mặc dù điều này có thể trở nên khó khăn, nếu các sửa đổi là không thể có tên phương thức ngắn gọn.
Michiel van Oosterhout

2
@michielvoo: nếu việc đặt tên phương thức đồng ý dường như là không thể, phương thức của bạn sẽ nhóm các thứ sai lại với nhau thay vì xây dựng một sự trừu tượng hóa chức năng cho nhiệm vụ mà nó thực hiện (và điều đó đúng với hoặc không có tác dụng phụ).
Doc Brown

4

Có, hãy xem http://codebetter.com/matthewpodwysocki/2008/04/30/side-effecting-fifts-are-code-smells/ để biết một trong nhiều ví dụ về những người chỉ ra rằng tác dụng phụ không mong muốn là xấu.

Nói chung, nguyên tắc cơ bản là phần mềm được xây dựng theo từng lớp và mỗi lớp sẽ trình bày sự trừu tượng sạch nhất có thể cho lớp kế tiếp. Và một sự trừu tượng sạch sẽ là một trong đó bạn phải giữ càng ít càng tốt trong tâm trí để sử dụng nó. Đó gọi là mô đun hóa, và áp dụng cho mọi thứ, từ các chức năng đơn lẻ đến các giao thức được nối mạng.


Tôi sẽ mô tả những gì OP mô tả là "tác dụng phụ dự kiến". Ví dụ: một đại biểu bạn có thể chuyển cho một công cụ thuộc loại nào đó hoạt động trên từng thành phần trong danh sách. Đó là những gì cơ bản ForEach<T>.
Robert Harvey

@RobertHarvey Những phàn nàn về các phương pháp không được đặt tên đúng và về việc phải đọc mã để tìm ra các tác dụng phụ, khiến chúng chắc chắn không được mong đợi là tác dụng phụ.
btilly

Tôi sẽ cấp cho bạn điều đó. Nhưng điều tất yếu là một phương pháp được ghi tên đúng với các hiệu ứng trang web dự kiến ​​có thể không phải là một mô hình chống lại.
Robert Harvey

@RobertHarvey Tôi đồng ý. Điều quan trọng là các tác dụng phụ đáng kể là rất quan trọng để biết và cần phải được ghi chép cẩn thận (tốt nhất là trong tên của phương pháp).
btilly

Tôi có thể nói đó là sự pha trộn giữa các tác dụng phụ không mong muốn và không rõ ràng. Cảm ơn các liên kết.
Michiel van Oosterhout

3

Trước hết, điều này không phụ thuộc vào "bản chất tham chiếu", nó phụ thuộc vào các đối tượng là các loại tham chiếu có thể thay đổi. Trong các ngôn ngữ phi chức năng đó hầu như luôn luôn là trường hợp.

Thứ hai, cho dù đây có phải là vấn đề hay không, phụ thuộc vào cả đối tượng và mức độ thay đổi của các quy trình khác nhau được gắn chặt với nhau - nếu bạn không thực hiện thay đổi trong Foo và điều đó khiến Bar gặp sự cố, thì đó là một vấn đề. Không nhất thiết là mùi mã, nhưng đó là vấn đề với Foo hoặc Bar hoặc Something (có lẽ là Bar vì nó cần kiểm tra đầu vào của nó, nhưng nó có thể là thứ gì đó được đưa vào trạng thái không hợp lệ mà nó nên ngăn chặn).

Tôi sẽ không nói rằng nó tăng đến mức chống mẫu, mà là một cái gì đó để nhận biết.


2

Tôi cho rằng có rất ít sự khác biệt giữa A.Do(Something)sửa đổi somethingsomething.Do()sửa đổi something. Trong cả hai trường hợp, cần phải rõ ràng từ tên của phương thức được gọi somethingsẽ được sửa đổi. Nếu nó không rõ ràng từ tên phương thức, bất kể somethinglà tham số thishay là một phần của môi trường, thì nó không nên được sửa đổi.


1

Tôi nghĩ sẽ tốt khi thay đổi trạng thái của đối tượng trong một số tình huống. Ví dụ: tôi có một danh sách người dùng và tôi muốn áp dụng các bộ lọc khác nhau cho danh sách trước khi trả lại cho khách hàng.

var users = Dependency.Resolve<IGetUsersQuery>().GetAll();

var excludeAdminUsersFilter = new ExcludeAdminUsersFilter();
var filterByAnotherCriteria = new AnotherCriteriaFilter();

excludeAdminUsersFilter.Apply(users);
filterByAnotherCriteria.Apply(users); 

Và vâng, bạn có thể làm cho điều này trở nên đẹp hơn bằng cách chuyển bộ lọc sang một phương thức khác, vì vậy bạn sẽ kết thúc với một cái gì đó trên dòng:

var users = Dependency.Resolve<IGetUsersQuery>().GetAll();
Filter(users);

Trường hợp Filter(users)sẽ thực hiện các bộ lọc trên.

Tôi không nhớ chính xác nơi tôi đã gặp phải điều này trước đây, nhưng tôi nghĩ nó được gọi là đường ống lọc.


0

Tôi không chắc chắn nếu giải pháp mới được đề xuất (của các đối tượng sao chép) là một mẫu. Vấn đề, như bạn đã chỉ ra, là danh pháp xấu của các hàm.

Hãy để chúng tôi nói rằng tôi viết một hoạt động toán học phức tạp như là một hàm f () . Tôi tài liệu rằng f () là một hàm ánh xạ NXNtới N, và thuật toán đằng sau nó. Nếu hàm được đặt tên không phù hợp, và không được ghi lại và không có trường hợp kiểm thử đi kèm, và bạn sẽ phải hiểu mã, trong trường hợp đó mã là vô dụng.

Về giải pháp của bạn, một số quan sát:

  • Các ứng dụng được thiết kế từ các khía cạnh khác nhau: khi một đối tượng chỉ được sử dụng để giữ các giá trị hoặc được chuyển qua các ranh giới thành phần, nên thay đổi bên ngoài đối tượng bên ngoài thay vì điền vào đó các chi tiết về cách thay đổi.
  • Các đối tượng nhân bản dẫn đến các yêu cầu bộ nhớ đầy hơi, và trong nhiều trường hợp dẫn đến sự tồn tại của các đối tượng tương đương ở trạng thái không tương thích ( Xtrở thành Ysau f(), nhưng Xthực ra là Yvậy) và có thể không nhất quán theo thời gian.

Vấn đề bạn đang cố gắng giải quyết là một vấn đề hợp lệ; tuy nhiên, ngay cả khi áp đảo quá lớn, vấn đề vẫn bị phá vỡ, không được giải quyết.


2
Đây sẽ là một câu trả lời tốt hơn nếu bạn liên quan đến quan sát của bạn với câu hỏi của OP. Như là, đó là một nhận xét nhiều hơn là một câu trả lời.
Robert Harvey

1
@RobertHarvey +1, quan sát tốt, tôi đồng ý, sẽ chỉnh sửa nó.
CMR
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.