Hai cấu trúc với cùng một thành viên nhưng đặt tên khác nhau, đó có phải là một ý tưởng tốt?


49

Tôi đang viết một chương trình liên quan đến việc làm việc với cả tọa độ cực và Cartesian.

Liệu nó có ý nghĩa để tạo ra hai cấu trúc khác nhau cho mỗi loại điểm, một với XYcác thành viên và một là với RThetacác thành viên.

Hoặc là quá nhiều và tốt hơn là chỉ có một cấu trúc với firstsecondlà thành viên.

Những gì tôi đang viết rất đơn giản và nó sẽ không thay đổi nhiều. Nhưng tôi tò mò không biết cái gì tốt hơn từ quan điểm thiết kế.

Tôi đang nghĩ lựa chọn đầu tiên là tốt hơn. Có vẻ dễ đọc hơn và tôi sẽ nhận được lợi ích của việc kiểm tra loại.


11
Tôi luôn tạo một cấu trúc / lớp mới cho mỗi mục đích. Cần một vector 3d, tạo một 3d_vector cấu trúc với ba float. Cần một đại diện uvw, tạo một struct_coords struct với ba float. Cần một vị trí trong môi trường 3d, tạo vị trí cấu trúc với ba phao. Bạn sẽ có được điểm. Nó cho phép dễ đọc hơn nhiều so với việc sử dụng cùng một thứ. Nếu bạn bị làm phiền bởi việc định nghĩa cùng một điều nhiều lần, hãy sử dụng cấu trúc 3 float cơ bản và xác định nhiều tên để có cùng cấu trúc đó.
Kevin

Nếu họ có một số phương pháp phổ biến thì có thể một. Bạn có bao giờ cần phải so sánh bình đẳng của hai?
paparazzo

8
@Sidney Trừ khi bạn thực sự cần chức năng tôi sẽ không. Bạn cần thực hiện thao tác sin / arcsin để chuyển đổi giữa hai biểu diễn. Điều này sẽ giới thiệu một chút bitrot trong các bit ít quan trọng nhất mỗi khi bạn thực hiện chuyển đổi. Tôi gần như chắc chắn rằng bạn sẽ kết thúc với nỗi đau tương tự với những gì tôi đã trải qua khi cố gắng đối phó với một lớp cung cấp cả tần suất sự kiện và thời gian giữa các sự kiện (x và 1 / x) đại diện cho một điều gì đó. Theo dõi đại diện nào là đại bác trong lớp và đối phó với tất cả những cơn đau đầu tròn trịa không phải là điều tôi muốn làm lại.
Dan Neely

3
Một ví dụ điển hình về kiểu dữ liệu có thể đại diện cho nhiều thứ là chuỗi, nhưng "chuỗi được gõ" là một phản mẫu. Viết ví dụ của bạn. Cố gắng triển khai sản phẩm chấm cho một loại hỗ trợ cả hai hệ tọa độ.
Nathan Cooper

1
Bạn nên cố gắng sử dụng các loại khác nhau bất cứ khi nào có thể, để có được lợi ích của an toàn loại - điều này sẽ ngăn bạn gửi sai loại đến / từ một chức năng (sử dụng kiểm tra độ chính xác thời gian biên dịch, tùy thuộc vào ngôn ngữ lập trình của bạn). Nó sẽ dễ dàng bảo trì bởi vì bạn có thể tìm thấy tất cả các cách sử dụng thực sự của một số loại (mà không sử dụng sai do các loại bị xáo trộn).
Erik Eidt

Câu trả lời:


17

Tôi đã thấy cả hai giải pháp, vì vậy nó chắc chắn phụ thuộc vào bối cảnh.

Để dễ đọc, có nhiều cấu trúc như bạn đề xuất là rất hiệu quả. Tuy nhiên, trong một số môi trường, bạn muốn thực hiện các thao tác phổ biến cho các cấu trúc này và bạn thấy mình đang sao chép mã, chẳng hạn như các hoạt động vectơ ma trận *. Nó có thể gây bực bội khi hoạt động cụ thể không có sẵn trong hương vị vector của bạn bởi vì không ai chuyển nó ở đó.

Giải pháp cực đoan (mà cuối cùng chúng tôi đã đưa ra) là có một lớp cơ sở khuôn mẫu CRTP với các hàm get <0> () get <1> () và lấy <2> () để lấy các phần tử theo cách chung. Các hàm này sau đó được định nghĩa trong cấu trúc Cartesian hoặc Polar xuất phát từ lớp cơ sở này. Nó giải quyết tất cả các vấn đề, nhưng có một mức giá khá ngớ ngẩn: phải học lập trình siêu mẫu. Tuy nhiên, nếu siêu lập trình mẫu là trò chơi công bằng cho dự án của bạn, thì đó có thể là một kết hợp tốt.


1
Câu trả lời của bạn rất thú vị. có thể cung cấp một ví dụ?
đà Moha toàn năng

1
Tôi có thể chứng minh một ví dụ về những gì tôi đã làm: FIR lọc polars, cartesian và vectơ của chúng. Toán học khá giống nhau, bao bọc góc (un), mã có một số phần được sao chép vì lý do hiệu suất và chúng tôi đã sử dụng các mẫu cho trường hợp giống nhau. Sử dụng tên khác nhau cho tất cả các công cụ. "Giải pháp cực đoan" của Cort có thể đã lưu một vài bản sao, nhưng không phải gần như tất cả chúng.
Eugene Ryabtsev

1
Phản ứng đầu tiên của tôi là một tình huống như thế này sẽ được giải quyết tốt hơn bằng cách đúc, nhưng hóa ra nó có phần mạo hiểm .
200_success

114

Vâng, nó rất có ý nghĩa.

Giá trị của một cấu trúc không chỉ là nó đóng gói dữ liệu dưới một tên tiện dụng. Giá trị là nó mã hóa ý định của bạn để trình biên dịch có thể giúp bạn xác minh rằng bạn không vi phạm chúng một ngày nào đó (ví dụ: nhầm một bộ tọa độ cực cho bộ tọa độ cartesian).

Mọi người rất tệ trong việc ghi nhớ những chi tiết gây cười như vậy, nhưng lại giỏi trong việc tạo ra những kế hoạch táo bạo, sáng tạo. Máy tính rất giỏi trong các chi tiết nghịch ngợm và xấu trong các kế hoạch sáng tạo. Do đó, luôn luôn là một ý tưởng tốt để chuyển nhiều bảo trì chi tiết rắc rối sang máy tính, để tâm trí của bạn tự do làm việc trong kế hoạch lớn.


6
+1 Một mô tả hoàn hảo về việc sử dụng máy tính để làm những gì máy tính giỏi, và để bộ não của bạn tập trung vào công việc của chính bạn.
BrianH

8
"Các chương trình nên được viết cho mọi người đọc và chỉ tình cờ cho các máy thực thi." - từ "Cấu trúc và diễn giải các chương trình máy tính" của Abelson và Sussman.
hlovdal

18

Đúng, trong khi cả Cartesian và Polar đều (ở vị trí của chúng) các sơ đồ biểu diễn tọa độ hợp lý rõ ràng, chúng không bao giờ được trộn lẫn (nếu bạn có một điểm Cartesian {1,1}, thì đó là một điểm rất khác so với Polar {1,1 }).

Tùy thuộc vào nhu cầu của bạn, nó cũng có thể có giá trị thực hiện một giao diện Phối hợp với các phương pháp như X(), Y(), Displacement()Angle()(hoặc có thể Radius()Theta(), tùy thuộc vào).


Điều thậm chí còn quan trọng hơn nếu OP tạo ra các lớp trong số các cấu trúc đó, vì các hoạt động trên tọa độ cartesian và cực khác nhau.
Mindwin

1
+1 cho đoạn cuối, đó là giải pháp lý tưởng. Một điểm là không gian là một đối tượng; đại diện nội bộ của điểm đó không quan trọng. Tất nhiên, mối quan tâm trong thế giới thực (hiệu suất, lỗi làm tròn) có thể khiến vấn đề trở nên quan trọng. Tất cả phụ thuộc vào những gì nó đang được sử dụng cho.
BlueRaja - Daniel Pflughoeft

Và, ví dụ này, không có khả năng thay đổi, nhưng nếu họ là 2 lớp khác, không có gì cho bạn biết rằng họ có thể phân kỳ ở một số điểm.
dyesdyes

8

Cuối cùng, mục tiêu của lập trình là chuyển các bit bóng bán dẫn để thực hiện công việc hữu ích. Nhưng suy nghĩ ở mức độ thấp như vậy sẽ dẫn đến sự điên rồ không thể kiểm soát, đó là lý do tại sao có các ngôn ngữ lập trình cấp cao hơn để giúp bạn che giấu sự phức tạp.

Nếu bạn chỉ tạo một cấu trúc với các thành viên được đặt tên firstsecond, thì tên đó không có nghĩa gì cả; về cơ bản bạn sẽ coi chúng là địa chỉ bộ nhớ. Điều đó đánh bại mục đích của ngôn ngữ lập trình cấp cao.

Hơn nữa, chỉ vì tất cả chúng đều có thể được đại diện vì doubleđiều đó không có nghĩa là bạn có thể sử dụng chúng thay thế cho nhau. Ví dụ, θ là góc không thứ nguyên, trong khi y có các đơn vị chiều dài. Vì các loại không thể thay thế một cách hợp lý, chúng phải là hai cấu trúc không tương thích.

Nếu bạn thực sự cần chơi các thủ thuật tái sử dụng bộ nhớ - và bạn gần như chắc chắn không nên - bạn có thể sử dụng một unionloại trong C để làm rõ ý định của mình.


Câu trả lời hay nhất, IMHO
Dean Radcliffe

2

Thứ nhất, có cả hai câu trả lời rõ ràng, theo câu trả lời hoàn toàn đúng đắn của @ Kilian-foth.

Tuy nhiên, tôi muốn thêm:

Hỏi: Bạn có thực sự có các hoạt động chung cho cả hai khi được coi là cặp double? Lưu ý điều này không giống như nói rằng bạn có các thao tác áp dụng cho cả hai theo cách riêng của chúng. Ví dụ: 'cốt truyện (Tọa độ)' quan tâm xem đó Coordlà Polar hay Cartesian. Mặt khác, kiên trì nộp hồ sơ chỉ xử lý dữ liệu như hiện tại. Nếu bạn thực sự có hoạt động chung, hãy xem xét một trong hai xác định một lớp cơ sở, hoặc xác định một chuyển đổi để một std::pair<double, double>hoặc tuplehoặc bất cứ điều gì bạn có trong ngôn ngữ của bạn.

Hơn nữa, một cách tiếp cận có thể là coi một loại Phối hợp là cơ bản hơn và loại khác chỉ đơn thuần là hỗ trợ cho người dùng hoặc tương tác bên ngoài.

Vì vậy, bạn có thể đảm bảo tất cả các hoạt động cơ bản được mã hóa Cartesianvà sau đó cung cấp hỗ trợ để chuyển đổi Polarsang Cartesian. Điều này tránh thực hiện các phiên bản khác nhau của nhiều hoạt động.


1

Một giải pháp khả thi, tùy thuộc vào ngôn ngữ và nếu bạn biết rằng cả hai lớp sẽ có các phương thức và thao tác tương tự nhau, sẽ là định nghĩa lớp một lần và sử dụng các bí danh loại để đặt tên rõ ràng cho các loại khác nhau.

Điều này cũng có một ưu điểm là miễn là các lớp giống hệt nhau, bạn chỉ có thể duy trì một lớp, nhưng ngay khi bạn cần thay đổi chúng, bạn không cần phải sửa đổi mã bằng cách sử dụng chúng vì các loại đã được sử dụng phân biệt

Một tùy chọn khác, tùy thuộc vào việc sử dụng các lớp (nếu bạn cần đa hình và như vậy) là sử dụng kế thừa công khai cho cả hai loại mới, vì vậy chúng có cùng giao diện chung như loại chung mà cả hai đại diện. Điều này cũng cho phép tiến hóa riêng biệt của các loại.


Tên của các thành viên trong cả hai lớp không giống nhau. Thật ra, tên là sự khác biệt duy nhất giữa hai lớp
lạc đà toàn năng Moha

@ Mhd.Tahawi Bạn có thể triển khai getters và setters với tên thích hợp, đảm bảo rằng lớp bạn đang sử dụng giống nhau, nhưng cung cấp tên thích hợp cho các hoạt động bạn muốn sử dụng. Nó trở nên dài dòng hơn một chút, nhưng bạn sẽ phải sao chép ít mã hơn.
Svalorzen

0

Tôi tin rằng có cùng tên thành viên là một ý tưởng tồi trong trường hợp này, vì nó làm cho mã dễ bị lỗi hơn.

Hãy tưởng tượng kịch bản: bạn có một vài điểm cartesian: pntA và pntB. Sau đó, bạn quyết định, vì một số lý do, tốt hơn là chúng nên được biểu diễn theo tọa độ cực và thay đổi khai báo và hàm tạo.

Bây giờ, nếu tất cả các hoạt động của bạn chỉ là các cuộc gọi phương thức như:

double distance = pntA.distanceFrom(pntB);

sau đó bạn ổn Nhưng nếu bạn sử dụng các thành viên một cách rõ ràng thì sao? So sánh

double leftMargin = abs(pntA.x - pntB.x);
double leftMargin = abs(pntA.first - pntB.first);

Trong trường hợp đầu tiên, mã sẽ không biên dịch. Bạn sẽ thấy lỗi ngay lập tức và sẽ có thể sửa nó. Nhưng nếu bạn có cùng tên thành viên, lỗi sẽ chỉ ở mức logic, khó phát hiện hơn nhiều.

Nếu bạn viết bằng ngôn ngữ không hướng đối tượng, thì việc chuyển cấu trúc sai sang hàm sẽ dễ dàng hơn. Điều gì ngăn bạn viết mã sau đây?

double distance = calculate_distance_polar(cartesianPointA, polarPointB);

Mặt khác, các loại dữ liệu khác nhau sẽ cho phép bạn tìm ra lỗi trong quá trình biên dịch.

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.