Làm cách nào để triển khai kế thừa RealNumber và ComplexNumber?


11

Hy vọng không quá hàn lâm ...

Giả sử tôi cần số thực và số phức trong thư viện SW của mình.

Dựa trên mối quan hệ is-a (hoặc ở đây ), số thực là một số phức, trong đó b trong phần ảo của số phức chỉ đơn giản là 0.

Mặt khác, việc triển khai của tôi sẽ là, đứa trẻ đó mở rộng cha mẹ, vì vậy trong cha mẹ RealNumber tôi có phần thực và đứa trẻ ComplexNumber sẽ thêm nghệ thuật tưởng tượng.

Ngoài ra có một ý kiến, rằng thừa kế là xấu xa .

Tôi nhớ lại như ngày hôm qua, khi tôi học OOP ở trường đại học, giáo sư của tôi nói, đây không phải là một ví dụ tốt về thừa kế vì giá trị tuyệt đối của hai thứ đó được tính khác nhau (nhưng vì chúng ta có quá tải phương pháp / đa hình, phải không?) .. .

Kinh nghiệm của tôi là, chúng ta thường sử dụng tính kế thừa để giải quyết DRY, do đó chúng ta thường có các lớp trừu tượng nhân tạo trong hệ thống phân cấp (chúng ta thường gặp vấn đề khi tìm tên vì chúng không đại diện cho các đối tượng từ thế giới thực).


7
Điều này trông giống như được đề cập trong câu hỏi trước: Hình chữ nhật có nên kế thừa từ hình vuông không?
gnat

1
@gnat Ôi trời, đó là một ví dụ khác tôi muốn sử dụng ... Cảm ơn!
Betlista

7
... Lưu ý rằng câu "số thực là một số phức" theo nghĩa toán học chỉ có giá trị đối với các số bất biến , vì vậy nếu bạn sử dụng các đối tượng bất biến, bạn có thể tránh vi phạm LSP (cũng giữ các hình vuông và hình chữ nhật, xem phần này Trả lời SO ).
Doc Brown

5
... Lưu ý thêm cách tính giá trị tuyệt đối cho số phức cũng hoạt động với số thực, vì vậy tôi không chắc giáo sư của bạn có ý gì. Nếu bạn triển khai một phương thức "abs ()" một cách chính xác với số phức bất biến và lấy được "thực" từ nó, phương thức abs () vẫn sẽ mang lại kết quả chính xác.
Doc Brown

Câu trả lời:


17

Ngay cả trong một ý nghĩa toán học, một số thực là một số phức, thì việc lấy thực tế từ số phức không phải là một ý tưởng hay. Nó vi phạm Nguyên tắc thay thế Liskov (trong số những thứ khác) rằng một lớp dẫn xuất không nên che giấu các thuộc tính của một lớp cơ sở.

Trong trường hợp này, một số thực sẽ phải ẩn phần ảo của số phức. Rõ ràng là không có ý nghĩa gì khi lưu trữ một số dấu phẩy động ẩn (phần ảo) nếu bạn chỉ cần phần thực.

Về cơ bản, đây là vấn đề tương tự như ví dụ hình chữ nhật / hình vuông được đề cập trong một bình luận.


2
Hôm nay tôi đã thấy "Nguyên tắc thay thế Liskow" này nhiều lần, tôi sẽ phải đọc thêm về nó, vì tôi không biết điều đó.
Betlista

7
Hoàn toàn ổn khi báo cáo phần ảo của một số thực là 0, ví dụ như thông qua phương pháp chỉ đọc. Nhưng thật vô nghĩa khi thực hiện một số thực như một số phức trong đó phần ảo được đặt thành không. Đây chính xác là một trường hợp trong đó kế thừa là sai lệch: trong khi kế thừa giao diện có thể được cho là tốt ở đây, kế thừa thực hiện sẽ dẫn đến một thiết kế có vấn đề.
amon

4
Nó có ý nghĩa hoàn hảo để có số thực thừa hưởng từ số phức, miễn là cả hai đều bất biến. Và bạn không quan tâm đến chi phí.
Ded repeatator

@Ded repeatator: Điểm thú vị. Bất biến giải quyết rất nhiều vấn đề nhưng tôi chưa hoàn toàn bị thuyết phục trong trường hợp này. Phải suy nghĩ về nó.
Frank Puffer

3

không phải là một ví dụ tốt về thừa kế vì giá trị tuyệt đối của hai cái đó được tính khác nhau

Đây thực sự không phải là một lý do thuyết phục chống lại tất cả sự kế thừa ở đây, chỉ là mô hình class RealNumber<-> được đề xuất class ComplexNumber.

Bạn có thể xác định một cách hợp lý một giao diện Number, cả hai RealNumberComplexNumbersẽ thực hiện.

Điều đó có thể trông giống như

interface Number
{
    Number Add(Number rhs);
    Number Subtract(Number rhs);
    // ... etc
}

Nhưng sau đó, bạn muốn giới hạn các Numbertham số khác trong các hoạt động này thành cùng một loại dẫn xuất như this, mà bạn có thể tiến gần đến

interface Number<T>
{
    Number<T> Add(Number<T> rhs);
    Number<T> Subtract(Number<T> rhs);
    // ... etc
}

Hoặc thay vào đó, bạn sử dụng một ngôn ngữ cho phép đa hình cấu trúc, thay vì đa hình phụ. Đối với trường hợp cụ thể của các số, bạn có thể chỉ cần khả năng quá tải các toán tử số học.

complex operator + (complex lhs, complex rhs);
complex operator - (complex lhs, complex rhs);
// ... etc

Number frobnicate<Number>(List<Number> foos, Number bar); // uses arithmetic operations

0

Giải pháp: Không có RealNumberlớp học công cộng

Tôi sẽ thấy nó hoàn toàn ổn nếu ComplexNumbercó một phương thức tĩnh nhà máy fromDouble(double)sẽ trả về một số phức với số ảo là 0. Sau đó, bạn có thể sử dụng tất cả các hoạt động mà bạn sẽ sử dụng trên một RealNumberví dụ trong ComplexNumbertrường hợp này .

Nhưng tôi gặp khó khăn khi thấy lý do tại sao bạn muốn / cần phải có một RealNumberlớp kế thừa công khai . Thông thường thừa kế được sử dụng vì những lý do này (ra khỏi đầu tôi, sửa tôi nếu bỏ sót một số)

  • mở rộng hành vi. RealNumberskhông thể thực hiện bất kỳ thao tác bổ sung nào mà số phức không thể làm được, vì vậy không có điểm nào trong việc này.

  • thực hiện hành vi trừu tượng với một thực hiện cụ thể. Vì ComplexNumberkhông nên trừu tượng nên điều này cũng không áp dụng.

  • tái sử dụng mã. Nếu bạn chỉ sử dụng ComplexNumberlớp bạn sử dụng lại 100% mã.

  • thực hiện cụ thể / hiệu quả / chính xác hơn cho một nhiệm vụ cụ thể. Điều này có thể được áp dụng ở đây, RealNumberscó thể thực hiện một số chức năng nhanh hơn. Nhưng sau đó, lớp con này nên được ẩn đằng sau tĩnh fromDouble(double)và không nên biết bên ngoài. Bằng cách này, nó sẽ không cần phải che giấu phần tưởng tượng. Đối với bên ngoài chỉ nên có số phức (số thực là số nào). Bạn cũng có thể trả về lớp RealNumber riêng này từ bất kỳ hoạt động nào trong lớp số phức dẫn đến số thực. (Điều này giả định rằng các lớp là bất biến như hầu hết các lớp số.)

Nó giống như thực hiện một lớp con của Integer được gọi là Zero và mã hóa cứng một số thao tác vì chúng không quan trọng bằng không. Bạn có thể làm điều này, vì mỗi số 0 là một số nguyên, nhưng đừng đặt nó ở chế độ công khai, hãy ẩn nó đằng sau một phương thức xuất xưởng.


Tôi không ngạc nhiên khi nhận được một số downvote, vì tôi không có nguồn để chứng minh. Ngoài ra nếu không có ai có ý tưởng, tôi luôn nghi ngờ có thể có một số lý do cho điều đó. Nhưng xin vui lòng cho tôi biết lý do tại sao bạn nghĩ rằng nó là sai và làm thế nào bạn sẽ làm cho nó tốt hơn.
findusl

0

Nói rằng một số thực là một số phức có ý nghĩa hơn trong toán học, đặc biệt là lý thuyết tập hợp, hơn là khoa học máy tính.
Trong toán học, chúng tôi nói:

  • Số thực là một số phức vì tập hợp các số phức bao gồm tập hợp các số thực.
  • Số hữu tỷ là số thực vì tập hợp các số thực bao gồm tập hợp các số hữu tỷ (và tập hợp các số vô tỷ).
  • Một số nguyên là một số hữu tỷ vì tập hợp các số hữu tỷ bao gồm tập các số nguyên.

Tuy nhiên, điều này không có nghĩa là bạn phải, hoặc thậm chí nên sử dụng tính kế thừa khi thiết kế thư viện của bạn để bao gồm lớp RealNumber và ComplexNumber. Trong Java hiệu quả, Ấn bản thứ hai của Joshua Bloch; Mục 16 là "Thành phần ưu tiên hơn kế thừa". Để tránh các vấn đề được đề cập trong mục đó, khi bạn đã xác định lớp RealNumber của mình, nó có thể được sử dụng trong lớp ComplexNumber của bạn:

public class ComplexNumber {
    private RealNumber realPart;
    private RealNumber imaginaryPart;

    // Implementation details are for you to write
}

Điều này cho phép bạn sử dụng toàn bộ lớp RealNumber của mình để giữ mã DRY trong khi tránh các vấn đề được xác định bởi Joshua Bloch.


0

Có hai vấn đề ở đây. Đầu tiên là việc sử dụng các thuật ngữ tương tự cho các loại container và các loại nội dung của chúng, đặc biệt với các loại nguyên thủy như số. doubleVí dụ, thuật ngữ này được sử dụng để mô tả cả giá trị dấu phẩy động có độ chính xác kép và vùng chứa trong đó một giá trị có thể được lưu trữ.

Vấn đề thứ hai là trong khi mối quan hệ giữa các container mà từ đó các loại đối tượng khác nhau có thể được đọc giống như mối quan hệ giữa các đối tượng, thì giữa các container có thể đặt các loại đối tượng khác nhau đối diện với các nội dung của chúng . Mỗi lồng được biết là chứa một thể hiện Catsẽ là một cái lồng chứa một thể hiện của Animal, nhưng không cần phải là một cái lồng chứa một thể hiện của SiameseCat. Mặt khác, mọi lồng có thể chứa tất cả các thể hiện Catsẽ là một lồng có thể chứa tất cả các thể hiện của SiameseCat, nhưng không cần phải là một lồng có thể chứa tất cả các thể hiện của Animal. Loại lồng duy nhất có thể chứa tất cả các thể hiện Catvà có thể được đảm bảo không bao giờ giữ bất cứ thứ gì ngoài một thể hiện củaCat, là một cái lồng của Cat. Bất kỳ loại lồng nào khác đều không có khả năng chấp nhận một số trường hợp Catmà nó nên chấp nhận hoặc có thể chấp nhận những thứ không phải là trường hợp Cat.

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.