2008-09-23 25 views
20

Tôi sẽ chọn Java làm ví dụ, hầu hết mọi người đều biết điều đó, mặc dù mọi ngôn ngữ OO khác cũng đang hoạt động.Có phải OOP & hoàn toàn tránh thừa kế thực hiện có thể không?

Java, giống như nhiều ngôn ngữ khác, có kế thừa giao diện và kế thừa thực hiện. Ví dụ. một lớp Java có thể kế thừa từ một phương thức khác và mọi phương thức có thực hiện ở đó (giả sử cha mẹ không trừu tượng) cũng được kế thừa. Điều đó có nghĩa là giao diện được kế thừa và thực hiện cho phương thức này. Tôi có thể ghi đè lên nó, nhưng tôi không phải. Nếu tôi không ghi đè lên nó, tôi đã thừa kế việc thực hiện.

Tuy nhiên, lớp học của tôi cũng có thể "kế thừa" (không phải trong thuật ngữ Java) chỉ là một giao diện, mà không cần triển khai. Trên thực tế các giao diện thực sự được đặt tên theo cách đó trong Java, chúng cung cấp sự thừa kế giao diện, nhưng không thừa hưởng bất kỳ việc triển khai thực hiện nào, vì tất cả các phương thức của một giao diện không thực hiện.

Bây giờ đã có số này article, saying it's better to inherit interfaces than implementations, bạn có thể muốn đọc nó (ít nhất là nửa đầu của trang đầu tiên), nó khá thú vị. Nó tránh các vấn đề như fragile base class problem. Cho đến nay điều này làm cho tất cả rất nhiều ý nghĩa và nhiều điều khác nói trong bài viết làm cho rất nhiều ý nghĩa với tôi.

Điều gì làm phiền tôi về điều này, đó là thừa kế thực hiện nghĩa là sử dụng lại mã tái sử dụng, một trong những thuộc tính quan trọng nhất của ngôn ngữ OO. Bây giờ nếu Java không có các lớp (như James Gosling, bố già của Java đã mong muốn theo bài viết này), nó giải quyết mọi vấn đề về kế thừa thực hiện, nhưng làm thế nào bạn sẽ làm cho việc tái sử dụng mã có thể sau đó?

Ví dụ: nếu tôi có một lớp Xe hơi và Xe hơi có phương pháp di chuyển(), điều này làm cho Xe di chuyển. Bây giờ tôi có thể phân loại xe hơi cho các loại xe khác nhau, đó là tất cả các xe hơi, nhưng tất cả đều là phiên bản chuyên biệt của ô tô. Một số có thể di chuyển theo một cách khác, những cần phải ghi đè lên move() anyway, nhưng hầu hết chỉ đơn giản là giữ di chuyển thừa kế, khi chúng di chuyển giống hệt như cha mẹ trừu tượng. Bây giờ giả sử trong một giây chỉ có các giao diện trong Java, chỉ các giao diện có thể kế thừa từ nhau, một lớp có thể thực hiện các giao diện, nhưng tất cả các lớp luôn là cuối cùng, vì vậy không có lớp nào có thể kế thừa từ bất kỳ lớp nào khác.

Làm thế nào bạn tránh được điều đó khi bạn có một chiếc xe giao diện và hàng trăm lớp học xe hơi, bạn cần phải thực hiện một phương thức move() giống hệt nhau cho mỗi người trong số họ? Khái niệm tái sử dụng mã nào khác hơn là thừa kế thực hiện tồn tại trong thế giới OO?

Một số ngôn ngữ có Mixins. Mixins câu trả lời cho câu hỏi của tôi? Tôi đã đọc về chúng, nhưng tôi không thể tưởng tượng Mixins sẽ làm việc như thế nào trong một thế giới Java và nếu chúng thực sự có thể giải quyết được vấn đề ở đây.

Một ý tưởng khác là có một lớp chỉ thực hiện giao diện ô tô, hãy gọi nó là AbstractCar và triển khai phương thức move(). Bây giờ những chiếc xe khác cũng thực hiện giao diện Xe, trong nội bộ chúng tạo ra một thể hiện của AbstractCar và chúng thực hiện phương thức move() của riêng chúng bằng cách gọi move() trên Xe trừu tượng bên trong của chúng. Nhưng điều này sẽ không lãng phí tài nguyên cho không có gì (một phương thức gọi một phương thức khác - không sao, JIT có thể in mã, nhưng vẫn còn) và sử dụng bộ nhớ thừa để giữ các đối tượng bên trong, thậm chí bạn không cần phải thừa kế thực hiện? (sau khi tất cả các đối tượng cần nhiều bộ nhớ hơn là tổng của dữ liệu đóng gói) Cũng không phải là nó khó xử cho một lập trình viên để viết phương pháp giả như

public void move() { 
    abstractCarObject.move(); 
} 

?

Bất cứ ai cũng có thể tưởng tượng một ý tưởng tốt hơn cách tránh kế thừa triển khai và vẫn có thể sử dụng lại mã theo cách dễ dàng?

+0

Tôi không thích gọi nó là "giao diện thừa kế" nhưng "giao diện triển khai". Đó là một điều tôi thực sự thích từ ngôn ngữ Java, hai khái niệm khác nhau được đặt tên cho phù hợp. – Trap

Trả lời

11

Câu trả lời ngắn gọn: Có thể thực hiện được. Nhưng bạn phải làm điều đó vào mục đích và không có một cách tình cờ (sử dụng cuối cùng, trừu tượng và thiết kế với thừa kế trong tâm trí, vv)

Long trả lời:

Vâng, thừa kế không phải là thực sự cho "mã tái sử dụng ", nó là dành cho lớp" chuyên môn ", tôi nghĩ đây là một sự giải thích sai.

Ví dụ: một ý tưởng rất tồi tệ là tạo một Ngăn xếp từ một Vector, chỉ vì chúng giống nhau. Hoặc các thuộc tính từ HashTable chỉ vì chúng lưu trữ các giá trị. Xem [Hiệu quả].

"Sử dụng lại mã" có nhiều "đặc điểm kinh doanh" của các đặc tính OO, có nghĩa là các đối tượng của bạn dễ dàng phân phối giữa các nút; và được di chuyển và không có vấn đề về thế hệ ngôn ngữ lập trình trước đó. Điều này đã được chứng minh một nửa rigth. Bây giờ chúng tôi có các thư viện có thể dễ dàng phân phối; ví dụ trong java các tệp jar có thể được sử dụng trong bất kỳ dự án nào tiết kiệm hàng nghìn giờ phát triển. OO vẫn có một số vấn đề với tính di động và những thứ tương tự, đó là lý do tại sao các dịch vụ Web rất phổ biến (như trước đây là CORBA) nhưng đó là một chủ đề khác.

Đây là một khía cạnh của "sử dụng lại mã". Cách khác là có hiệu quả, một trong đó đã làm với lập trình.Nhưng trong trường hợp này không chỉ là để "lưu" các dòng mã và tạo ra những con quái vật mong manh, mà còn thiết kế với sự thừa kế trong tâm trí. Đây là mục 17 trong cuốn sách đã đề cập trước đây; Mục 17: Thiết kế và tài liệu thừa kế hoặc người khác cấm. Xem [Hiệu quả]

Tất nhiên bạn có thể có một lớp Ô tô và tấn phân lớp. Và có, cách tiếp cận bạn đề cập đến về giao diện xe hơi, AbstractCar và CarImplementation là một cách chính xác để đi.

Bạn xác định "hợp đồng" xe nên tuân thủ và nói đây là những phương pháp tôi mong đợi có khi nói về xe ô tô. Chiếc xe trừu tượng có chức năng cơ sở mà mỗi chiếc xe nhưng để lại và ghi lại các phương pháp mà các lớp con chịu trách nhiệm xử lý. Trong java bạn làm điều này bằng cách đánh dấu phương thức là abstract.

Khi bạn tiến hành theo cách này, không có vấn đề gì với lớp "mong manh" (hoặc ít nhất nhà thiết kế có ý thức hoặc mối đe dọa) và các lớp con chỉ hoàn thành những phần mà nhà thiết kế cho phép.

Thừa kế là nhiều hơn để "chuyên" các lớp học, trong cùng một thời trang một chiếc xe tải là một phiên bản chuyên biệt của xe hơi, và MosterTruck một phiên bản chuyên biệt của xe tải.

Nó không làm cho sanse tạo ra một "ComputerMouse" subclase từ một chiếc xe chỉ vì nó có một bánh xe (bánh xe cuộn) như một chiếc xe, nó di chuyển, và có một bánh xe dưới đây chỉ để lưu dòng mã. Nó thuộc về một tên miền khác, và nó sẽ được sử dụng cho các mục đích khác.

Cách ngăn chặn sự thừa kế "triển khai" bằng ngôn ngữ lập trình ngay từ đầu, bạn nên sử dụng từ khóa cuối cùng trên khai báo lớp và theo cách này bạn đang cấm các lớp con.

Phân lớp không phải là điều ác nếu được thực hiện đúng mục đích. Nếu nó được thực hiện không công bằng nó có thể trở thành một cơn ác mộng. Tôi sẽ nói rằng bạn nên bắt đầu như tư nhân và "cuối cùng" càng tốt và nếu cần thiết làm cho mọi thứ công khai hơn và có thể mở rộng. Điều này cũng được giải thích rộng rãi trong bài thuyết trình "Cách thiết kế các API tốt và tại sao nó quan trọng" Xem [Good API]

Giữ bài viết và thời gian và thực hành (và rất nhiều kiên nhẫn) điều này sẽ trở nên rõ ràng hơn. Mặc dù đôi khi bạn chỉ cần thực hiện công việc và sao chép/dán một số mã: P. Điều này là ok, miễn là bạn cố gắng làm tốt trước.

Sau đây là các tài liệu tham khảo cả từ Joshua Bloch (trước đây làm việc tại Sun là cốt lõi của java hiện đang làm việc cho Google)


[hiệu quả] Effective Java. Chắc chắn cuốn sách java tốt nhất không phải là người mới bắt đầu nên học, hiểu và thực hành. A phải có.

Effective Java


[Tốt API] Bài thuyết trình rằng cuộc đàm phán về thiết kế của API, tái sử dụng và các chủ đề liên quan. Đó là một chút dài nhưng nó có giá trị mỗi phút.

How To Design A Good API and Why it Matters

Kính trọng.


Cập nhật: Hãy xem phút 42 của liên kết video tôi đã gửi cho bạn.Nó nói về chủ đề này:

"Khi bạn có hai lớp trong một API công cộng và bạn nghĩ để làm cho một lớp con của một lớp khác, như Foo là phân lớp của Bar, hãy tự hỏi mình, là Mỗi Foo a Bar ?. .. "

Và trong phút trước nó nói về" sử dụng lại mã "trong khi nói về TimeTask.

+0

Nghiêm túc, tôi không thấy làm thế nào một chiếc xe tải nên kế thừa từ một chiếc xe hơi :) – Trap

+2

Có lẽ Xe tải và Ô tô nên kế thừa từ ô tô. :-) – StriplingWarrior

4

Bạn cũng có thể sử dụng bố cục và mẫu chiến lược. link text

public class Car 
{ 
    private ICar _car; 

    public void Move() { 
    _car.Move(); 
    } 
} 

này còn lâu mới linh hoạt hơn so với sử dụng hành vi dựa thừa kế vì nó cho phép bạn thay đổi trong thời gian chạy, bằng cách thay thế các loại xe mới theo yêu cầu.

2

Bạn có thể sử dụng composition. Trong ví dụ của bạn, một đối tượng Car có thể chứa một đối tượng khác có tên là Drivetrain. Phương thức move() của xe có thể đơn giản gọi phương thức drive() của hệ thống truyền động của nó. Nếu bạn cấu trúc phân cấp lớp theo cách này, bạn có thể dễ dàng tạo ra những chiếc xe di chuyển theo những cách khác nhau bằng cách tạo ra các kết hợp khác nhau của các phần đơn giản hơn (ví dụ: sử dụng lại mã).

0

Bạn nên đọc Mẫu thiết kế. Bạn sẽ thấy rằng Giao diện rất quan trọng đối với nhiều loại Mẫu thiết kế hữu ích. Ví dụ trừu tượng hóa các loại giao thức mạng khác nhau sẽ có cùng giao diện (với phần mềm gọi nó) nhưng ít sử dụng lại mã vì các hành vi khác nhau của từng loại giao thức.

Đối với một số thuật toán được mở mắt trong việc hiển thị cách kết hợp các yếu tố vô số của một chương trình để thực hiện một số tác vụ hữu ích. Các mẫu thiết kế làm tương tự cho các đối tượng. Hiển thị cho bạn cách kết hợp các đối tượng theo cách để thực hiện một tác vụ hữu ích.

Design Patterns by the Gang of Four

2

Để làm mixins/thành phần dễ dàng hơn, hãy nhìn vào chú thích của tôi và xử lý chú giải:

http://code.google.com/p/javadude/wiki/Annotations

Đặc biệt, mixins dụ:

http://code.google.com/p/javadude/wiki/AnnotationsMixinExample

Lưu ý rằng nó hiện không hoạt động nếu giao diện aces/types được ủy nhiệm để có các phương thức tham số (hoặc các kiểu tham số hóa trên các phương thức). Tôi đang làm việc trên ...

+0

Đây là một số công cụ khá thú vị, bạn có ở đó. Tôi sẽ xem xét chi tiết nó :) – Mecki

8

Vấn đề với hầu hết ví dụ về kế thừa là ví dụ trong trường hợp người đó đang sử dụng thừa kế không chính xác, không phải là lỗi thừa kế để trừu tượng chính xác.

Trong bài viết bạn đã đăng liên kết tới, tác giả hiển thị "sự hỏng" của thừa kế bằng cách sử dụng Stack và ArrayList. Ví dụ này là thiếu sót vì ngăn xếp không phải là một ArrayList và do đó không được sử dụng thừa kế. Ví dụ này là thiếu sót như Chuỗi mở rộng Ký tự hoặc Số mở rộng PointXY.

Trước khi bạn mở rộng lớp học, bạn nên luôn thực hiện bài kiểm tra "is_a". Vì bạn không thể nói Every Stack là một ArrayList mà không bị sai theo cách nào đó, thì bạn không nên hít vào.

Hợp đồng cho ngăn xếp khác với hợp đồng cho ArrayList (hoặc danh sách) và ngăn xếp không được kế thừa các phương thức không quan tâm (như get (int i) và add()). Trong thực tế stack phải là một giao diện với các phương pháp như:

interface Stack<T> { 
    public void push(T object); 
    public T pop(); 
    public void clear(); 
    public int size(); 
} 

Một lớp học như ArrayListStack có thể thực hiện các giao diện Stack, và trong đó thành phần trường hợp sử dụng (có một ArrayList nội bộ) và không thừa kế.

Thừa kế không phải là xấu, thừa kế xấu là xấu.

0

Thừa kế không cần thiết cho ngôn ngữ hướng đối tượng.

Hãy xem xét Javascript, thậm chí còn hướng đối tượng nhiều hơn Java, được cho là. Không có lớp học, chỉ là các đối tượng. Mã được tái sử dụng bằng cách thêm các phương thức hiện có vào một đối tượng. Một đối tượng Javascript về bản chất là một bản đồ của các tên cho các hàm (và dữ liệu), nơi các nội dung ban đầu của bản đồ được thiết lập bởi một mẫu thử nghiệm và các mục mới có thể được thêm vào một cá thể đã cho.

2

Thật vui khi trả lời câu hỏi của riêng tôi, nhưng đây là điều tôi thấy thú vị: Sather.

Đó là ngôn ngữ lập trình không có thừa kế triển khai nào cả! Nó biết các giao diện (được gọi là các lớp trừu tượng không có dữ liệu thực hiện hoặc đóng gói), và các giao diện có thể kế thừa lẫn nhau (thực ra chúng thậm chí còn hỗ trợ đa thừa kế!), Nhưng một lớp chỉ có thể thực hiện các giao diện (các lớp trừu tượng, nhiều như nó thích), nó không thể kế thừa từ một lớp khác. Tuy nhiên, nó có thể "bao gồm" một lớp khác. Đây là một khái niệm đại biểu. Các lớp được bao gồm phải được khởi tạo trong hàm tạo của lớp của bạn và bị hủy khi lớp của bạn bị hủy. Trừ khi bạn ghi đè lên các phương thức mà họ có, lớp của bạn cũng thừa hưởng giao diện của họ, nhưng không phải mã của họ. Thay vào đó, các phương thức được tạo ra chỉ chuyển tiếp các cuộc gọi đến phương thức của bạn đến phương thức được đặt tên giống nhau của đối tượng được bao gồm. Sự khác biệt giữa các đối tượng được bao gồm và các đối tượng được đóng gói là bạn không phải tạo ra ủy nhiệm chuyển tiếp bản thân và chúng không tồn tại như các đối tượng độc lập mà bạn có thể truyền đi, chúng là một phần của đối tượng của bạn và sống và chết cùng với Đối tượng của bạn và tất cả những đối tượng được bao gồm được tạo ra với một cuộc gọi cấp phát đơn lẻ, cùng một khối bộ nhớ, bạn chỉ cần khởi tạo chúng trong lời gọi hàm dựng, trong khi khi sử dụng các đại biểu thực, mỗi đối tượng này một cuộc gọi phân bổ riêng, có một khối bộ nhớ riêng và hoàn toàn độc lập với đối tượng của bạn).

Ngôn ngữ không đẹp, nhưng tôi thích ý tưởng đằng sau nó :-)

Các vấn đề liên quan