Có một tên cho mẫu (chống) của các tham số truyền sẽ chỉ được sử dụng ở một số cấp độ sâu trong chuỗi cuộc gọi không?


209

Tôi đã cố gắng tìm giải pháp thay thế cho việc sử dụng biến toàn cục trong một số mã kế thừa. Nhưng câu hỏi này không phải là về các lựa chọn kỹ thuật, tôi chủ yếu quan tâm đến thuật ngữ .

Giải pháp rõ ràng là truyền một tham số vào hàm thay vì sử dụng toàn cục. Trong cơ sở mã di sản này có nghĩa là tôi phải thay đổi tất cả các hàm trong chuỗi cuộc gọi dài giữa điểm cuối cùng giá trị sẽ được sử dụng và hàm nhận tham số trước.

higherlevel(newParam)->level1(newParam)->level2(newParam)->level3(newParam)

trong newParamđó trước đây là một biến toàn cục trong ví dụ của tôi, nhưng nó có thể là một giá trị được mã hóa trước đó. Vấn đề là bây giờ giá trị của newParam đã đạt được higherlevel()và phải "di chuyển" mọi cách level3().

Tôi đã tự hỏi nếu có một tên cho loại tình huống / mẫu này trong đó bạn cần thêm một tham số cho nhiều hàm chỉ "truyền" giá trị không được sửa đổi.

Hy vọng rằng, sử dụng thuật ngữ thích hợp sẽ cho phép tôi tìm thêm tài nguyên về các giải pháp để thiết kế lại và mô tả tình huống này cho các đồng nghiệp.


94
Đây là một cải tiến so với việc sử dụng các biến toàn cầu. Nó làm cho nó rõ ràng chính xác trạng thái của từng chức năng phụ thuộc vào (và là một bước trên con đường đến các chức năng thuần túy). Tôi đã nghe nó được gọi là "luồng" một tham số thông qua, nhưng tôi không biết thuật ngữ này phổ biến như thế nào.
vườn

8
Đây là loại phổ quá rộng để có câu trả lời cụ thể. Ở cấp độ này, tôi gọi đây chỉ là "mã hóa".
Machado

38
Tôi nghĩ rằng "vấn đề" chỉ là sự đơn giản. Đây là cơ bản tiêm phụ thuộc. Tôi đoán có thể có các cơ chế sẽ tự động đưa ra sự phụ thuộc thông qua chuỗi, nếu một số thành viên lồng nhau sâu hơn có nó, mà không làm đầy danh sách tham số của các hàm. Có thể nhìn vào các chiến lược tiêm phụ thuộc với mức độ tinh vi khác nhau có thể dẫn đến thuật ngữ bạn đang tìm kiếm, nếu có.
null

7
Mặc dù tôi đánh giá cao cuộc thảo luận về việc liệu đó có phải là một mô hình / phản mẫu / khái niệm / giải pháp tốt hay không, nhưng điều tôi thực sự muốn biết là liệu có một cái tên cho nó hay không.
ecerulm

3
Tôi cũng đã nghe thấy nó được gọi là luồng thông thường nhất, nhưng cũng có hệ thống ống nước , như trong việc hạ thấp một đường thẳng đứng trong suốt ngăn xếp cuộc gọi.
wchargein

Câu trả lời:


202

Dữ liệu được gọi là "dữ liệu tramp" . Đó là một "mùi mã", chỉ ra rằng một đoạn mã đang giao tiếp với một đoạn mã khác ở khoảng cách xa, thông qua các trung gian.

  • Tăng độ cứng của mã, đặc biệt là trong chuỗi cuộc gọi. Bạn bị ràng buộc nhiều hơn trong cách bạn cấu trúc lại bất kỳ phương thức nào trong chuỗi cuộc gọi.
  • Phân phối kiến ​​thức về dữ liệu / phương pháp / kiến ​​trúc cho những nơi không quan tâm đến nó ít nhất. Nếu bạn cần khai báo dữ liệu vừa đi qua và khai báo yêu cầu nhập mới, bạn đã làm ô nhiễm không gian tên.

Tái cấu trúc để loại bỏ các biến toàn cục là khó khăn và dữ liệu tramp là một phương pháp để làm như vậy và thường là cách rẻ nhất. Nó có chi phí của nó.


73
Bằng cách tìm kiếm "dữ liệu tramp", tôi có thể tìm thấy cuốn sách "Hoàn thành mã" trên đăng ký Safari của mình. Có một phần trong cuốn sách có tên "Lý do sử dụng dữ liệu toàn cầu" và một trong những lý do là "Sử dụng dữ liệu toàn cầu có thể loại bỏ dữ liệu của người đi lang thang". :). Tôi cảm thấy như "dữ liệu tramp" sẽ cho phép tôi tìm thêm tài liệu về giao dịch với toàn cầu. Cảm ơn!
ecerulm

9
@JimmyJames, những chức năng đó làm công cụ tất nhiên. Không phải với thông số mới cụ thể mà trước đây chỉ là toàn cầu.
ecerulm

174
Trong 20 năm lập trình, tôi thực sự chưa bao giờ nghe thuật ngữ này trước đây và cũng không rõ ràng về ý nghĩa của nó. Tôi không phàn nàn về câu trả lời, chỉ gợi ý rằng thuật ngữ này không được sử dụng rộng rãi / được biết đến. Có lẽ đó chỉ là tôi.
Derek Elkins

6
Một số dữ liệu toàn cầu là tốt. Thay vì gọi nó là "dữ liệu toàn cầu", bạn có thể gọi nó là "môi trường" - bởi vì đó là những gì nó là. Ví dụ, môi trường có thể bao gồm một đường dẫn chuỗi cho appdata (trên windows) hoặc trong dự án hiện tại của tôi, toàn bộ bộ bút vẽ GDI +, bút, phông chữ, v.v., được sử dụng bởi tất cả các thành phần.
Robinson

7
@Robinson Không hoàn toàn. Ví dụ: bạn có thực sự muốn mã viết hình ảnh của mình chạm vào% AppData% không, hay bạn muốn nó tranh luận về nơi để viết? Đó là sự khác biệt giữa nhà nước toàn cầu và một cuộc tranh luận. "Môi trường" có thể dễ dàng trở thành một phụ thuộc được tiêm, chỉ xuất hiện cho những người chịu trách nhiệm tương tác với môi trường. Bàn chải GDI +, v.v ... hợp lý hơn, nhưng đó thực sự là một trường hợp quản lý tài nguyên trong môi trường không thể làm điều đó cho bạn - gần như thiếu các API cơ bản và / hoặc ngôn ngữ / thư viện / thời gian chạy của bạn.
Luaan

102

Tôi không nghĩ rằng, bản thân nó, là một mô hình chống. Tôi nghĩ vấn đề là bạn đang nghĩ về các chức năng như một chuỗi khi thực sự bạn nên nghĩ mỗi cái là một hộp đen độc lập ( LƯU Ý : phương pháp đệ quy là một ngoại lệ đáng chú ý đối với lời khuyên này.)

Ví dụ: giả sử tôi cần tính số ngày giữa hai ngày theo lịch để tôi tạo một hàm:

int daysBetween(Day a, Day b)

Để làm điều này, sau đó tôi tạo một chức năng mới:

int daysSinceEpoch(Day day)

Sau đó, chức năng đầu tiên của tôi trở nên đơn giản:

int daysBetween(Day a, Day b)
{
    return daysSinceEpoch(b) - daysSinceEpoch(a);
}

Không có gì chống mẫu về điều này. Các tham số của phương thức daysB between đang được truyền sang một phương thức khác và không bao giờ được tham chiếu theo phương thức khác nhưng chúng vẫn cần cho phương thức đó để làm những gì nó cần làm.

Những gì tôi muốn giới thiệu là xem xét từng chức năng và bắt đầu với một vài câu hỏi:

  • Liệu chức năng này có một mục tiêu rõ ràng và tập trung hay nó là một phương pháp "làm một số việc"? Thông thường tên của hàm giúp ở đây và nếu có thứ trong đó không được mô tả bằng tên, thì đó là cờ đỏ.
  • Có quá nhiều thông số? Đôi khi một phương thức có thể cần rất nhiều đầu vào nhưng việc có quá nhiều tham số khiến nó trở nên nặng nề khi sử dụng hoặc hiểu.

Nếu bạn đang xem một mớ lộn xộn của mã mà không có một mục đích nào được đưa vào một phương thức, bạn nên bắt đầu bằng cách làm sáng tỏ điều đó. Điều này có thể tẻ nhạt. Bắt đầu với những điều dễ nhất để rút ra và chuyển sang một phương thức riêng biệt và lặp lại cho đến khi bạn có một cái gì đó mạch lạc.

Nếu bạn chỉ có quá nhiều tham số, hãy xem xét tái cấu trúc Phương thức để Đối tượng .


2
Chà, tôi không có ý tranh cãi với (chống). Nhưng tôi vẫn tự hỏi liệu có một tên cho "tình huống" phải cập nhật nhiều chữ ký chức năng. Tôi đoán là nhiều "mùi mã" hơn là một antipotype. Nó cho tôi biết rằng có một cái gì đó được sửa trong mã kế thừa này, nếu tôi phải cập nhật 6 chữ ký chức năng để phù hợp với việc loại bỏ toàn cầu. Nhưng tôi nghĩ rằng việc truyền tham số thường là ổn, và tôi đánh giá cao sự tiến bộ về cách giải quyết vấn đề tiềm ẩn.
ecerulm

4
@ecerulm Tôi không biết về một người nhưng tôi sẽ nói rằng kinh nghiệm của tôi cho tôi biết rằng chuyển đổi toàn cầu thành tham số là cách hoàn toàn đúng đắn để bắt đầu loại bỏ chúng. Điều này giúp loại bỏ trạng thái chia sẻ để bạn có thể tái cấu trúc thêm. Tôi đoán có nhiều vấn đề hơn với mã này nhưng không đủ trong mô tả của bạn để biết chúng là gì.
JimmyJames

2
Tôi cũng thường làm theo cách tiếp cận đó, và có lẽ cũng sẽ làm trong trường hợp này. Tôi chỉ muốn cải thiện vốn từ vựng / thuật ngữ của mình xung quanh vấn đề này để tôi có thể nghiên cứu thêm về vấn đề này và làm những câu hỏi tốt hơn, tập trung hơn trong tương lai.
ecerulm

3
@ecerulm Tôi không nghĩ có một cái tên cho cái này. Nó giống như một triệu chứng phổ biến đối với nhiều bệnh cũng như các tình trạng không phải là bệnh, ví dụ như 'khô miệng'. Nếu bạn đưa ra mô tả về cấu trúc của mã, nó có thể chỉ ra một cái gì đó cụ thể.
JimmyJames

@ecerulm Nó cho bạn biết có một cái gì đó cần được sửa chữa - bây giờ rõ ràng hơn là một cái gì đó sẽ được sửa chữa hơn so với khi nó là một biến toàn cầu thay thế.
Immibis

61

BobDalgleish đã lưu ý rằng mẫu (chống) này được gọi là " dữ liệu tramp ".

Theo kinh nghiệm của tôi, nguyên nhân phổ biến nhất của dữ liệu tramp quá mức là có một loạt các biến trạng thái được liên kết nên thực sự được gói gọn trong một đối tượng hoặc cấu trúc dữ liệu. Đôi khi, thậm chí có thể cần phải lồng một loạt các đối tượng để tổ chức dữ liệu đúng cách.

Ví dụ đơn giản, hãy xem xét một trò chơi có nhân vật người chơi có thể tùy chỉnh, với các thuộc tính như playerName, playerEyeColorv.v. Tất nhiên, người chơi cũng có một vị trí vật lý trên bản đồ trò chơi và nhiều thuộc tính khác như, mức độ sức khỏe hiện tại và tối đa, v.v.

Trong lần lặp đầu tiên của một trò chơi như vậy, có thể là một lựa chọn hoàn toàn hợp lý để biến tất cả các thuộc tính này thành các biến toàn cục - sau tất cả, chỉ có một người chơi và hầu hết mọi thứ trong trò chơi đều liên quan đến người chơi. Vì vậy, trạng thái toàn cầu của bạn có thể chứa các biến như:

playerName = "Bob"
playerEyeColor = GREEN
playerXPosition = -8
playerYPosition = 136
playerHealth = 100
playerMaxHealth = 100

Nhưng tại một số điểm, bạn có thể thấy rằng bạn cần thay đổi thiết kế này, có lẽ vì bạn muốn thêm chế độ nhiều người chơi vào trò chơi. Lần thử đầu tiên, bạn có thể thử biến tất cả các biến đó thành cục bộ và chuyển chúng đến các hàm cần chúng. Tuy nhiên, sau đó bạn có thể thấy rằng một hành động cụ thể trong trò chơi của bạn có thể liên quan đến chuỗi cuộc gọi chức năng như, nói:

mainGameLoop()
 -> processInputEvent()
     -> doPlayerAction()
         -> movePlayer()
             -> checkCollision()
                 -> interactWithNPC()
                     -> interactWithShopkeeper()

... và interactWithShopkeeper()chức năng này có người bán hàng gọi địa chỉ của người chơi theo tên, vì vậy bây giờ bạn đột nhiên cần truyền playerNamedữ liệu dưới dạng thông qua tất cả các chức năng đó. Và, tất nhiên, nếu chủ cửa hàng nghĩ rằng những người chơi mắt xanh là ngây thơ, và sẽ tính giá cao hơn cho họ, thì bạn cần phải playerEyeColorthông qua toàn bộ chuỗi chức năng, v.v.

Tất nhiên , giải pháp thích hợp là xác định một đối tượng người chơi gói gọn tên, màu mắt, vị trí, sức khỏe và bất kỳ thuộc tính nào khác của nhân vật người chơi. Bằng cách đó, bạn chỉ cần chuyển đối tượng duy nhất đó cho tất cả các chức năng liên quan đến người chơi.

Ngoài ra, một số chức năng trên có thể được tạo tự nhiên thành các phương thức của đối tượng người chơi đó, nó sẽ tự động cấp cho họ quyền truy cập vào các thuộc tính của người chơi. Theo một cách nào đó, đây chỉ là đường cú pháp, vì việc gọi một phương thức trên một đối tượng có hiệu quả vượt qua thể hiện đối tượng như một tham số ẩn cho phương thức, nhưng nó làm cho mã trông rõ ràng và tự nhiên hơn nếu được sử dụng đúng cách.

Tất nhiên, một trò chơi thông thường sẽ có trạng thái "toàn cầu" hơn nhiều so với chỉ người chơi; ví dụ: bạn gần như chắc chắn có một loại bản đồ mà trò chơi diễn ra và một danh sách các nhân vật không phải người chơi di chuyển trên bản đồ, và có thể các vật phẩm được đặt trên đó, v.v. Bạn cũng có thể vượt qua tất cả những người xung quanh dưới dạng các đối tượng lang thang, nhưng điều đó sẽ lại làm lộn xộn các đối số phương thức của bạn.

Thay vào đó, giải pháp là để các đối tượng lưu trữ các tham chiếu đến bất kỳ đối tượng nào khác mà chúng có mối quan hệ vĩnh viễn hoặc tạm thời. Vì vậy, ví dụ, đối tượng người chơi (và có thể là bất kỳ đối tượng NPC nào) có lẽ nên lưu trữ một tham chiếu đến đối tượng "thế giới trò chơi", có tham chiếu đến cấp độ / bản đồ hiện tại, do đó, một phương thức như player.moveTo(x, y)không cần phải được cung cấp rõ ràng bản đồ như là một tham số.

Tương tự, nếu nhân vật người chơi của chúng ta có một con chó cưng đi theo họ xung quanh, chúng ta sẽ tự nhiên nhóm tất cả các biến trạng thái mô tả con chó thành một đối tượng và cung cấp cho đối tượng người chơi một tham chiếu đến con chó (để người chơi có thể , nói, gọi con chó theo tên) và ngược lại (để con chó biết người chơi đang ở đâu). Và, tất nhiên, có lẽ chúng ta muốn làm cho người chơi và các đối tượng chó cả hai lớp con của một đối tượng "diễn viên" chung chung hơn, để chúng ta có thể sử dụng lại cùng một mã cho, di chuyển cả hai trên bản đồ.

Thi thiên Mặc dù tôi đã sử dụng một trò chơi làm ví dụ, có những loại chương trình khác cũng xuất hiện những vấn đề như vậy. Tuy nhiên, theo kinh nghiệm của tôi, vấn đề tiềm ẩn có xu hướng luôn giống nhau: bạn có một loạt các biến riêng biệt (dù là cục bộ hay toàn cầu) thực sự muốn được gộp lại thành một hoặc nhiều đối tượng liên kết với nhau. Cho dù "dữ liệu tramp" xâm nhập vào các chức năng của bạn bao gồm các cài đặt tùy chọn "toàn cầu" hoặc truy vấn cơ sở dữ liệu được lưu trong bộ nhớ cache hoặc vectơ trạng thái trong một mô phỏng số, thì giải pháp luôn luôn xác định bối cảnh tự nhiên mà dữ liệu thuộc về và biến nó thành một đối tượng (hoặc bất cứ điều gì tương đương gần nhất trong ngôn ngữ bạn đã chọn).


1
Câu trả lời này cung cấp một số giải pháp cho một lớp các vấn đề có thể tồn tại. Có thể có những tình huống mà các quả cầu được sử dụng sẽ chỉ ra một giải pháp khác. Tôi có một vấn đề với ý tưởng rằng làm cho các phương thức trở thành một phần của lớp trình phát tương đương với việc truyền các đối tượng cho các phương thức. Điều này bỏ qua tính đa hình không dễ dàng lặp lại theo cách này. Ví dụ, nếu tôi muốn tạo các loại trình phát khác nhau có các quy tắc khác nhau về chuyển động và các loại thuộc tính khác nhau, chỉ cần chuyển các đối tượng này sang một phương thức thực hiện sẽ đòi hỏi rất nhiều logic có điều kiện.
JimmyJames

6
@JimmyJames: Quan điểm của bạn về đa hình là một điều tốt, và tôi đã nghĩ về việc tự làm nó, nhưng bỏ qua nó để giữ cho câu trả lời thậm chí còn lâu hơn. Điểm mà tôi đã cố gắng (có lẽ là kém) để đưa ra là, trong khi hoàn toàn về mặt luồng dữ liệu, có rất ít sự khác biệt giữa foo.method(bar, baz)method(foo, bar, baz), có những lý do khác (bao gồm đa hình, đóng gói, địa phương, v.v.) để thích cái trước.
Ilmari Karonen

@IlmariKaronen: cũng là lợi ích rất rõ ràng mà nó chứng minh trong tương lai các nguyên mẫu hàm từ bất kỳ thay đổi / bổ sung / xóa / tái cấu trúc nào trong các đối tượng (ví dụ: playerAge). Điều này một mình là vô giá.
smci

34

Tôi không biết tên cụ thể cho việc này, nhưng tôi đoán rằng điều đáng nói là vấn đề bạn mô tả chỉ là vấn đề tìm ra sự thỏa hiệp tốt nhất cho phạm vi của một tham số như vậy:

  • là một biến toàn cục, phạm vi quá lớn khi chương trình đạt đến một kích thước nhất định

  • như một tham số cục bộ thuần túy, phạm vi có thể quá nhỏ, khi nó dẫn đến nhiều danh sách tham số lặp lại trong chuỗi cuộc gọi

  • vì vậy, như một sự đánh đổi, bạn thường có thể biến một tham số như vậy thành một biến thành viên trong một hoặc nhiều lớp và đó là điều mà tôi sẽ gọi là thiết kế lớp thích hợp .


10
+1 cho thiết kế lớp thích hợp. Điều này nghe có vẻ như là một vấn đề kinh điển đang chờ giải pháp OO.
l0b0

21

Tôi tin rằng mô hình bạn mô tả là chính xác tiêm phụ thuộc . Một số ý kiến ​​đã tranh luận rằng đây là một mô hình , không phải là một mô hình chống , và tôi sẽ có xu hướng đồng ý.

Tôi cũng đồng tình với câu trả lời của @ JimmyJames, nơi anh ta tuyên bố rằng đó là cách thực hành lập trình tốt để coi mỗi hàm là một hộp đen lấy tất cả các đầu vào của nó làm tham số rõ ràng. Đó là, nếu bạn đang viết một chức năng tạo ra một bánh sandwich bơ đậu phộng và thạch, bạn có thể viết nó như là

Sandwich make_sandwich() {
    PeanutButter pb = get_peanut_butter();
    Jelly j = get_jelly();
    return pb + j;
}
extern PhysicalRefrigerator g_refrigerator;
PeanutButter get_peanut_butter() {
    return g_refrigerator.get("peanut butter");
}
Jelly get_jelly() {
    return g_refrigerator.get("jelly");
}

nhưng nó sẽ là thực hành tốt hơn để áp dụng tiêm phụ thuộc và viết nó như thế này thay thế:

Sandwich make_sandwich(Refrigerator& r) {
    PeanutButter pb = get_peanut_butter(r);
    Jelly j = get_jelly(r);
    return pb + j;
}
PeanutButter get_peanut_butter(Refrigerator& r) {
    return r.get("peanut butter");
}
Jelly get_jelly(Refrigerator& r) {
    return r.get("jelly");
}

Bây giờ bạn có một chức năng ghi lại rõ ràng tất cả các phụ thuộc của nó trong chữ ký hàm của nó, rất tốt cho khả năng đọc. Rốt cuộc, đúng là để make_sandwichbạn cần truy cập vào một Refrigerator; Vì vậy, chữ ký chức năng cũ về cơ bản là không rõ ràng bằng cách không lấy tủ lạnh làm một phần của đầu vào.

Như một phần thưởng, nếu bạn thực hiện đúng thứ bậc lớp của mình, tránh cắt lát, v.v., bạn thậm chí có thể kiểm tra đơn vị make_sandwichchức năng bằng cách chuyển vào a MockRefrigerator! (Bạn có thể cần kiểm tra đơn vị theo cách này vì môi trường kiểm tra đơn vị của bạn có thể không có quyền truy cập vào bất kỳ PhysicalRefrigerators nào .)

Tôi hiểu rằng không phải tất cả các sử dụng tiêm phụ thuộc đều yêu cầu hệ thống thông số có tên tương tự nhiều cấp trong ngăn xếp cuộc gọi, vì vậy tôi không trả lời chính xác câu hỏi bạn đã hỏi ... nhưng nếu bạn đang tìm đọc thêm về chủ đề này, "tiêm phụ thuộc" chắc chắn là một từ khóa có liên quan cho bạn.


10
Đây là rất rõ ràng một mô hình chống . Hoàn toàn không có cuộc gọi cho một tủ lạnh được thông qua. Bây giờ, việc chuyển một Nguồn thành phần chung có thể có hiệu quả, nhưng nếu bạn lấy bánh mì từ bánh mì, cá ngừ từ người cho vay, phô mai từ tủ lạnh ... bằng cách tiêm một nguồn phụ thuộc vào nguồn nguyên liệu vào hoạt động không liên quan của chúng. thành phần vào một chiếc bánh sandwich, bạn đã vi phạm sự phân tách các mối quan tâm và tạo ra một mùi mã.
Dewi Morgan

8
@DewiMorgan: Rõ ràng bạn có thể tái cấu trúc hơn nữa để khái quát hóa Refrigeratorthành một IngredientSource, hoặc thậm chí khái quát hóa khái niệm "sandwich" thành template<typename... Fillings> StackedElementConstruction<Fillings...> make_sandwich(ElementSource&); cái đó được gọi là "lập trình chung" và nó có sức mạnh hợp lý, nhưng chắc chắn nó còn phức tạp hơn OP thực sự muốn tham gia ngay bây giờ. Hãy thoải mái mở một câu hỏi mới về mức độ trừu tượng thích hợp cho các chương trình bánh sandwich. ;)
Quuxplusone

11
Có mặt ở đó không có lỗi, người dùng không có đặc quyền nên không có quyền truy cập vào make_sandwich().
dotancohen


19
Lỗi nghiêm trọng nhất trong mã của bạn là bạn đang giữ bơ đậu phộng trong tủ lạnh.
Malvolio

15

Đây là khá nhiều định nghĩa của sách giáo khoa về khớp nối , một mô-đun có một phụ thuộc ảnh hưởng sâu sắc đến một mô-đun khác và tạo ra hiệu ứng gợn khi thay đổi. Các ý kiến ​​và câu trả lời khác đều đúng rằng đây là một sự cải tiến trên toàn cầu, bởi vì việc ghép nối giờ đây rõ ràng hơn và dễ dàng hơn cho người lập trình, thay vì lật đổ. Điều đó không có nghĩa là nó không nên được sửa chữa. Bạn sẽ có thể tái cấu trúc để loại bỏ hoặc giảm khớp nối, mặc dù nếu nó ở đó trong một thời gian nó có thể gây đau đớn.


3
Nếu level3()cần newParam, đó là khớp nối chắc chắn, nhưng bằng cách nào đó, các phần khác nhau của mã phải giao tiếp với nhau. Tôi sẽ không nhất thiết gọi một tham số chức năng khớp xấu nếu chức năng đó sử dụng tham số. Tôi nghĩ rằng khía cạnh có vấn đề của chuỗi là khớp nối bổ sung được giới thiệu level1()level2()không có công dụng nào newParamngoại trừ việc truyền nó. Câu trả lời tốt, +1 cho khớp nối.
null

6
@null Nếu họ thực sự không sử dụng nó, họ có thể tạo ra một giá trị thay vì nhận từ người gọi của họ.
Random832

3

Mặc dù câu trả lời này không trả lời trực tiếp câu hỏi của bạn, tôi cảm thấy tôi sẽ hối hận khi để nó trôi qua mà không đề cập đến cách cải thiện nó (vì như bạn nói, nó có thể là một mô hình chống đối). Tôi hy vọng rằng bạn và các độc giả khác có thể nhận được giá trị từ bình luận bổ sung này về cách tránh "dữ liệu tramp" (vì Bob Dalgleish rất hữu ích đặt tên cho chúng tôi).

Tôi đồng ý với câu trả lời đề nghị làm một cái gì đó OO nhiều hơn để tránh vấn đề này. Tuy nhiên, một cách khác cũng giúp giảm sâu việc truyền các đối số này mà không cần chuyển sang " chỉ cần vượt qua một lớp mà bạn đã sử dụng để vượt qua nhiều đối số! " Là tái cấu trúc sao cho một số bước của quy trình của bạn xảy ra ở cấp cao hơn thay vì thấp hơn một. Ví dụ: đây là một số trước khi mã:

public void PerformReporting(StuffRepository repo, string desiredName) {
   var stuffs = repo.GetStuff(DateTime.Now());
   FilterAndReportStuff(stuffs, desiredName);
}

public void FilterAndReportStuff(IEnumerable<Stuff> stuffs, string desiredName) {
   var filter = CreateStuffFilter(FilterTypes.Name, desiredName);
   ReportStuff(stuffs.Filter(filter));
}

public void ReportStuff(IEnumerable<Stuff> stuffs) {
   stuffs.Report();
}

Lưu ý rằng điều này càng trở nên tồi tệ hơn khi phải làm nhiều việc hơn ReportStuff. Bạn có thể phải vượt qua trong trường hợp Trình báo cáo bạn muốn sử dụng. Và tất cả các loại phụ thuộc phải được chuyển giao, chức năng cho chức năng lồng nhau.

Đề nghị của tôi là kéo tất cả những điều đó lên một mức cao hơn, trong đó kiến ​​thức về các bước đòi hỏi phải sống trong một phương thức duy nhất thay vì được truyền bá trong một chuỗi các cuộc gọi phương thức. Tất nhiên nó sẽ phức tạp hơn trong mã thực, nhưng điều này cho bạn một ý tưởng:

public void PerformReporting(StuffRepository repo, string desiredName) {
   var stuffs = repo.GetStuff(DateTime.Now());
   var filter = CreateStuffFilter(FilterTypes.Name, desiredName);
   var filteredStuffs = stuffs.Filter(filter)
   filteredStuffs.Report();
}

Lưu ý rằng sự khác biệt lớn ở đây là bạn không phải vượt qua sự phụ thuộc thông qua một chuỗi dài. Ngay cả khi bạn làm phẳng không chỉ một cấp, mà sâu một vài cấp, nếu các cấp đó cũng đạt được một số "làm phẳng" để quá trình được coi là một loạt các bước ở cấp đó, bạn sẽ có một sự cải thiện.

Mặc dù điều này vẫn còn mang tính thủ tục và chưa có gì được biến thành một đối tượng, nhưng đây là một bước tốt để quyết định loại đóng gói nào bạn có thể đạt được thông qua việc biến một thứ gì đó thành một lớp. Phương thức được xâu chuỗi sâu trong kịch bản trước đó ẩn các chi tiết về những gì đang thực sự xảy ra và có thể làm cho mã rất khó hiểu. Mặc dù bạn có thể lạm dụng điều này và cuối cùng làm cho mã cấp cao hơn biết về những điều không nên hoặc thực hiện một phương pháp thực hiện quá nhiều thứ do đó vi phạm nguyên tắc trách nhiệm đơn lẻ, nói chung tôi đã thấy rằng làm phẳng mọi thứ một chút trong sự rõ ràng và trong việc thực hiện thay đổi gia tăng đối với mã tốt hơn.

Lưu ý rằng trong khi bạn đang làm tất cả điều này, bạn nên xem xét khả năng kiểm tra. Các cuộc gọi phương thức xích thực sự làm cho việc kiểm tra đơn vị khó hơn vì bạn không có điểm vào và điểm thoát tốt trong cụm cho lát cắt mà bạn muốn kiểm tra. Lưu ý rằng với việc làm phẳng này, vì các phương thức của bạn không còn mất quá nhiều phụ thuộc, nên chúng dễ kiểm tra hơn, không yêu cầu nhiều giả!

Gần đây tôi đã cố gắng thêm các bài kiểm tra đơn vị vào một lớp (mà tôi không viết) mà đã lấy thứ gì đó như 17 phụ thuộc, tất cả đều phải bị chế giễu! Tôi vẫn chưa giải quyết xong, nhưng tôi chia lớp thành ba lớp, mỗi lớp liên quan đến một trong những danh từ riêng mà nó quan tâm và có danh sách phụ thuộc xuống còn 12 cho lớp xấu nhất và khoảng 8 cho tốt nhất

Khả năng kiểm tra sẽ buộc bạn phải viết mã tốt hơn. Bạn nên viết bài kiểm tra đơn vị vì bạn sẽ thấy rằng nó khiến bạn nghĩ về mã của mình khác đi và bạn sẽ viết mã tốt hơn từ việc di chuyển, bất kể bạn có thể gặp phải bao nhiêu lỗi trước khi viết bài kiểm tra đơn vị.


2

Bạn thực sự không vi phạm Luật Demeter, nhưng vấn đề của bạn cũng tương tự như vậy theo một số cách. Vì mục đích của câu hỏi của bạn là tìm tài nguyên, tôi khuyên bạn nên đọc về Law of Demeter và xem bao nhiêu lời khuyên đó áp dụng cho tình huống của bạn.


1
Một chút yếu về chi tiết, có lẽ giải thích các downvote. Tuy nhiên, về mặt tinh thần, câu trả lời này chính xác là: OP nên đọc về Luật của Demeter - đây là thuật ngữ có liên quan.
Konrad Rudolph

4
FWIW, tôi không nghĩ rằng Luật Demeter (hay còn gọi là "đặc quyền tối thiểu") có liên quan. Trường hợp của OP là nơi chức năng của anh ta sẽ không thể thực hiện công việc của mình nếu nó không có dữ liệu của người truy cập (vì người tiếp theo xuống ngăn xếp cuộc gọi cần nó, bởi vì người tiếp theo cần nó, v.v.). Đặc quyền tối thiểu / Law of Demeter chỉ có liên quan nếu tham số thực sự không được sử dụng và trong trường hợp đó, sửa chữa là rõ ràng: loại bỏ tham số không sử dụng!
Quuxplusone

2
Tình huống của câu hỏi này hoàn toàn không liên quan gì đến Luật Demeter ... Có một sự tương đồng bề ngoài liên quan đến chuỗi các cuộc gọi phương thức, nhưng nếu không thì nó rất khác.
Eric King

@Quuxplusone Có thể, mặc dù trong trường hợp này, mô tả khá khó hiểu vì các cuộc gọi bị xiềng xích không thực sự có ý nghĩa trong kịch bản đó: thay vào đó chúng nên được lồng vào nhau .
Konrad Rudolph

1
Vấn đề rất giống với vi phạm LoD, kể từ khi refactoring thường đề nghị để đối phó với hành vi vi phạm LoD là để giới thiệu dữ liệu kẻ lang thang. IMHO, đây là điểm khởi đầu tốt để giảm khớp nối nhưng nó không đủ.
Jørgen Fogh

1

Có những trường hợp tốt nhất (về hiệu quả, khả năng duy trì và dễ thực hiện) để có một số biến nhất định là toàn cầu thay vì chi phí luôn vượt qua mọi thứ xung quanh (giả sử bạn có 15 hoặc hơn các biến phải tồn tại). Vì vậy, thật hợp lý khi tìm ngôn ngữ lập trình hỗ trợ phạm vi tốt hơn (như các biến tĩnh riêng của C ++) để giảm bớt sự lộn xộn tiềm năng (của không gian tên và có những thứ bị can thiệp). Tất nhiên đây chỉ là kiến ​​thức phổ biến.

Nhưng, cách tiếp cận được OP nêu rất hữu ích nếu một người đang thực hiện Lập trình chức năng.


0

Không có mô hình chống ở đây, bởi vì người gọi không biết về tất cả các cấp độ dưới đây và không quan tâm.

Ai đó đang gọi highLevel (params) và mong đợi AdvancedLevel thực hiện công việc của mình. Những gì cao hơn làm với params là không ai trong số các doanh nghiệp gọi. elevLevel xử lý vấn đề theo cách tốt nhất có thể, trong trường hợp này bằng cách chuyển params đến level1 (params). Điều đó hoàn toàn ổn.

Bạn thấy một chuỗi cuộc gọi - nhưng không có chuỗi cuộc gọi. Có một chức năng ở đầu làm công việc của nó theo cách tốt nhất có thể. Và có các chức năng khác. Mỗi chức năng có thể được thay thế bất cứ lúc nào.

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.