Tái cấu trúc Báo cáo chuyển đổi và có sử dụng thực sự nào cho Báo cáo chuyển đổi không?


28

Tôi đã đọc bài viết này và tự hỏi, liệu chúng ta có loại bỏ tất cả các câu lệnh chuyển đổi bằng cách thay thế chúng bằng Từ điển hoặc Nhà máy để không có câu lệnh chuyển đổi nào trong các dự án của tôi không.

Một cái gì đó không hoàn toàn thêm vào.

Câu hỏi đặt ra là, liệu các câu lệnh chuyển đổi có sử dụng thực sự hay chúng ta sẽ tiếp tục và thay thế chúng bằng một từ điển hoặc một phương thức xuất xưởng (tất nhiên, khi sử dụng một phương thức xuất xưởng, sẽ có một cách sử dụng tối thiểu các câu lệnh chuyển đổi để tạo các đối tượng sử dụng nhà máy ... nhưng đó là về nó).


10
Giả sử bạn thực hiện một nhà máy, bạn sẽ quyết định loại đối tượng nào sẽ tạo ra?
CodeART


@CodeWorks: Tất nhiên, tôi sẽ có điều kiện ở đâu đó, quyết định nên sử dụng triển khai cụ thể nào.
Kanini

@CodeWorks: Với một nhà xây dựng ảo, rõ ràng. (Và nếu bạn không thể triển khai mô hình nhà máy theo cách đó, bạn cần một ngôn ngữ tốt hơn.)
Mason Wheeler

Câu trả lời:


44

Cả hai switchtuyên bố và đa hình đều có công dụng của chúng. Lưu ý rằng mặc dù cũng có một tùy chọn thứ ba (trong các ngôn ngữ hỗ trợ các con trỏ hàm / lambdas và các hàm bậc cao hơn): ánh xạ các định danh được đề cập đến các hàm xử lý. Điều này có sẵn trong ví dụ C không phải là ngôn ngữ OO và C # là *, nhưng không phải (chưa) trong Java cũng là OO *.

Trong một số ngôn ngữ thủ tục (không có hàm đa hình hay hàm bậc cao hơn) switch/ if-elsecâu lệnh là cách duy nhất để giải quyết một lớp các vấn đề. Vì vậy, nhiều nhà phát triển, đã quen với lối suy nghĩ này, tiếp tục sử dụng switchngay cả trong các ngôn ngữ OO, trong đó đa hình thường là một giải pháp tốt hơn. Đây là lý do tại sao nên tránh / tái cấu trúc các switchcâu lệnh có lợi cho đa hình.

Ở bất kỳ giá nào, giải pháp tốt nhất luôn phụ thuộc vào từng trường hợp. Câu hỏi là: tùy chọn nào cung cấp cho bạn mã sạch hơn, ngắn gọn hơn, dễ bảo trì hơn trong thời gian dài?

Báo cáo chuyển đổi thường có thể phát triển khó sử dụng, có hàng tá trường hợp, làm cho bảo trì của họ khó khăn. Vì bạn phải giữ chúng trong một chức năng duy nhất, chức năng đó có thể phát triển rất lớn. Nếu đây là trường hợp, bạn nên xem xét tái cấu trúc theo hướng giải pháp dựa trên bản đồ và / hoặc đa hình.

Nếu cùng switchbắt đầu bật lên ở nhiều nơi, đa hình có lẽ là lựa chọn tốt nhất để thống nhất tất cả các trường hợp này và đơn giản hóa mã. Đặc biệt là nếu nhiều trường hợp dự kiến ​​sẽ được thêm vào trong tương lai; bạn càng cần cập nhật nhiều nơi hơn, càng có nhiều khả năng xảy ra lỗi. Tuy nhiên, thường thì các trình xử lý trường hợp riêng lẻ rất đơn giản hoặc có rất nhiều trong số chúng hoặc có liên quan đến nhau, việc tái cấu trúc chúng thành một hệ thống phân cấp lớp đa hình đầy đủ là quá mức, hoặc dẫn đến rất nhiều mã trùng lặp và / hoặc bị rối, khó duy trì thứ bậc lớp. Trong trường hợp này, có thể đơn giản hơn khi sử dụng các hàm / lambdas (nếu ngôn ngữ của bạn cho phép bạn).

Tuy nhiên, nếu bạn có một switchnơi duy nhất, chỉ với một vài trường hợp làm điều gì đó đơn giản, đó có thể là giải pháp tốt nhất để rời khỏi nó như nó là.

* Tôi sử dụng thuật ngữ "OO" lỏng lẻo ở đây; Tôi không quan tâm đến các cuộc tranh luận về khái niệm về OO "thực" hay "thuần túy".


4
+1. Ngoài ra, những khuyến nghị đó có xu hướng được diễn đạt, " thích đa hình hơn để chuyển đổi" và đó là một lựa chọn từ tốt, rất phù hợp với câu trả lời này. Nó thừa nhận rằng có những trường hợp mà một lập trình viên có trách nhiệm có thể đưa ra lựa chọn khác.
Carl Manaster

1
Và có những lúc bạn muốn cách tiếp cận chuyển đổi ngay cả khi có một lượng lớn chúng trong mã của bạn. Tôi đã có một đoạn mã trong tâm trí tạo ra một mê cung 3D. Việc sử dụng bộ nhớ sẽ tăng lên nếu các ô một byte trong mảng được thay thế bằng các lớp.
Loren Pechtel

14

Đây là nơi tôi trở lại làm một con khủng long ...

Bản thân các báo cáo chuyển đổi không phải là xấu, đó là việc sử dụng chúng được tạo ra từ đó.

Điều rõ ràng nhất là câu lệnh chuyển đổi "giống nhau" lặp đi lặp lại nhiều lần thông qua mã của bạn rất tệ (ở đó, đã làm điều đó, sẽ khiến mọi nỗ lực không lặp lại) - và đây là trường hợp sau mà bạn có thể giải quyết với việc sử dụng đa hình. Thường cũng có một cái gì đó khá kinh khủng về các trường hợp lồng nhau (tôi đã từng có một con quái vật tuyệt đối - không hoàn toàn chắc chắn làm thế nào tôi đối phó với nó bây giờ ngoài "tốt hơn").

Từ điển dưới dạng chuyển đổi Tôi thấy khó khăn hơn - về cơ bản là có nếu công tắc của bạn bao gồm 100% các trường hợp, nhưng khi bạn muốn có mặc định hoặc không có trường hợp hành động thì nó bắt đầu thú vị hơn một chút.

Tôi nghĩ đó là một câu hỏi về việc tránh lặp lại và đảm bảo rằng chúng ta đang soạn đồ thị đối tượng của mình ở đúng nơi.

Nhưng cũng có đối số hiểu (khả năng duy trì) và điều này cắt giảm cả hai cách - một khi bạn hiểu cách thức hoạt động của tất cả (mô hình và ứng dụng mà nó triển khai) thì thật dễ dàng ... nhưng nếu bạn đến một dòng mã nơi bạn phải thêm một cái gì đó mới, sau đó bạn phải nhảy ra khắp nơi để tìm ra những gì bạn cần thêm / thay đổi.

Đối với tất cả các môi trường phát triển của chúng tôi có khả năng ồ ạt, tôi vẫn cảm thấy rằng có thể hiểu được mã (như thể nó) được in ra trên giấy - bạn có thể theo dõi mã bằng ngón tay không? Tôi chấp nhận rằng thực tế là không, với rất nhiều thực tiễn tốt hiện nay bạn không thể và vì những lý do chính đáng nhưng điều đó có nghĩa là bắt kịp tốc độ với mã khó hơn để bắt đầu (hoặc có lẽ tôi chỉ già ...)


4
Tôi đã có một giáo viên nói rằng mã lý tưởng nên được viết "để ai đó không biết gì về lập trình (như có thể là mẹ của bạn) có thể hiểu nó." Thật không may đó là một lý tưởng, nhưng có lẽ chúng ta nên bao gồm các bà mẹ của chúng tôi trong các đánh giá mã!
Michael K

+1 cho "nhưng nếu bạn đến một dòng mã duy nhất mà bạn phải thêm một cái gì đó mới thì bạn phải nhảy khắp nơi để tìm ra những gì bạn cần thêm / thay đổi"
quick_now

8

Báo cáo chuyển đổi so với đa hình phụ là một vấn đề cũ và thường được đề cập trong cộng đồng FP trong các cuộc thảo luận về Vấn đề Biểu hiện .

Về cơ bản, chúng ta có các kiểu (lớp) và hàm (phương thức). Làm thế nào để chúng ta viết mã để dễ dàng thêm các kiểu mới hoặc các phương thức mới sau này?

Nếu bạn lập trình theo kiểu OO, thật khó để thêm một phương thức mới (vì điều đó có nghĩa là tái cấu trúc tất cả các lớp hiện có), nhưng rất dễ dàng để thêm các lớp mới sử dụng các phương thức tương tự như trước đây.

Mặt khác, nếu bạn sử dụng câu lệnh chuyển đổi (hoặc tương đương OO của nó, Mẫu quan sát viên) thì rất dễ dàng để thêm các chức năng mới nhưng rất khó để thêm các trường hợp / lớp mới.

Không dễ để có khả năng mở rộng tốt theo cả hai hướng, vì vậy khi viết mã của bạn, hãy xác định xem nên sử dụng đa hình hay chuyển đổi câu lệnh tùy thuộc vào hướng nào bạn có khả năng mở rộng sau này.


4
chúng ta có loại bỏ tất cả các câu lệnh chuyển đổi bằng cách thay thế chúng bằng Từ điển hoặc Nhà máy để không có câu lệnh chuyển đổi nào trong các dự án của tôi không.

Không. Tuyệt đối như vậy hiếm khi là một ý tưởng tốt.

Nhiều nơi, một từ điển / tra cứu / nhà máy / công văn đa hình sẽ cung cấp một thiết kế tốt hơn so với một câu lệnh chuyển đổi nhưng bạn vẫn phải điền từ điển. Trong một số trường hợp nhất định, điều đó sẽ che khuất những gì đang thực sự xảy ra và chỉ cần có câu lệnh chuyển đổi trong dòng là dễ đọc và dễ bảo trì hơn.


Và thực sự, nếu bạn hủy đăng ký nhà máy và từ điển và tra cứu, thì về cơ bản nó sẽ là một câu lệnh chuyển đổi: nếu mảng [hash (tìm kiếm)] thì gọi mảng [hash (search)]. Mặc dù nó có thể mở rộng thời gian chạy mà các công tắc được biên dịch thì không.
Zan Lynx

@ZanLynx, hoàn toàn ngược lại là đúng, ít nhất là với C #. Nếu bạn nhìn vào IL được tạo ra cho một câu lệnh chuyển đổi không tầm thường, bạn sẽ thấy trình biên dịch biến nó thành một từ điển. Như vậy nó sẽ nhanh hơn.
David Arno

@DavidArno: "hoàn toàn ngược lại"? Cách tôi đọc nó, chúng tôi đã nói chính xác điều tương tự. Làm thế nào nó có thể ngược lại?
Zan Lynx

2

Xem làm thế nào đây là bất khả tri ngôn ngữ, mã thông qua hoạt động tốt nhất với các switchcâu lệnh:

switch(something) {
   case 1:
      foo();
   case 2:
      bar();
      baz();
      break;

   case 3:
      bang();
   default:
      bizzap();
      break;
}

Tương đương với if, với một trường hợp mặc định rất khó xử. Và hãy nhớ rằng càng có nhiều thông tin, danh sách điều kiện sẽ càng dài:

if (1 == something) {
   foo();
}
if (1 == something || 2 == something) {
   bar();
   baz();
}
if (3 == something) {
   bang();
}
if (1 != something && 2 != something) {
   bizzap();
}

(Tuy nhiên, với những gì một số câu trả lời khác nói, tôi cảm thấy như mình đã bỏ lỡ điểm của câu hỏi ...)


Tôi sẽ coi việc sử dụng các câu lệnh rơi trong một switchmức độ giống như a goto. Nếu thuật toán được thực hiện yêu cầu các luồng điều khiển phù hợp với các cấu trúc lập trình có cấu trúc, người ta nên sử dụng các cấu trúc đó, nhưng nếu thuật toán không phù hợp với các cấu trúc đó, sử dụng gotocó thể tốt hơn là cố gắng thêm cờ hoặc logic điều khiển khác để phù hợp với các cấu trúc điều khiển khác .
supercat

1

Thay đổi câu lệnh như phân biệt trường hợp có sử dụng thực sự. Các ngôn ngữ lập trình hàm sử dụng một cái gì đó gọi là khớp mẫu cho phép bạn xác định các hàm khác nhau tùy thuộc vào đầu vào. Tuy nhiên, trong các ngôn ngữ hướng đối tượng, cùng một mục tiêu có thể đạt được một cách thanh lịch hơn bằng cách sử dụng đa hình. Bạn chỉ cần gọi phương thức và tùy thuộc vào loại thực tế của đối tượng, việc thực hiện tương ứng của phương thức sẽ được thực thi. Đây là lý do đằng sau ý tưởng, rằng các câu lệnh chuyển đổi là một mùi mã. Tuy nhiên, ngay cả trong các ngôn ngữ OO, bạn có thể thấy chúng hữu ích để triển khai mẫu nhà máy trừu tượng.

tl; dr - chúng rất hữu ích, nhưng khá thường xuyên bạn có thể làm tốt hơn.


2
Tôi ngửi thấy một số sai lệch ở đó - tại sao đa hình thanh lịch hơn so với khớp mẫu? Và điều gì khiến bạn nghĩ rằng đa hình không tồn tại trong FP?
tdammers

@tdammers Trước hết tôi không có ý ám chỉ rằng tính đa hình không tồn tại trong FP. Haskell hỗ trợ đa hình ad hoc và tham số. Kết hợp mẫu có ý nghĩa đối với kiểu dữ liệu đại số. Nhưng đối với các mô-đun bên ngoài, điều này đòi hỏi phải có các nhà xây dựng. Rõ ràng điều này phá vỡ sự đóng gói của một kiểu dữ liệu trừu tượng (được thực hiện với các kiểu dữ liệu đại số). Đó là lý do tại sao tôi cảm thấy rằng đa hình là thanh lịch hơn (và có thể trong FP).
Scarfridge

Bạn đang quên tính đa hình thông qua các kiểu chữ và trường hợp.
tdammers

Heve bạn đã bao giờ nghe về vấn đề biểu hiện ? OO và FP tốt hơn trong các tình huống khác nhau, nếu bạn dừng lại để suy nghĩ về nó.
hugomg

@tdammers Bạn nghĩ tôi đã nói về loại đa hình nào khi tôi đề cập đến đa hình ad hoc? Tính đa hình ad hoc được hiện thực hóa trong Haskell thông qua các lớp loại. Làm thế nào bạn có thể nói tôi quên nó? Đơn giản là không đúng.
Scarfridge
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.