2009-10-05 43 views
5

Nếu tôi có các lớp con khác nhau và thuật toán hoạt động trên các cá thể của các lớp con đó, và nếu hành vi của thuật toán thay đổi một chút tùy thuộc vào trường hợp cụ thể của lớp con, thì cách hướng đối tượng thông thường nhất để thực hiện điều này đang sử dụng các phương pháp ảo. Ví dụ nếu các lớp con là nút DOM, và nếu thuật toán chèn một nút con, thuật toán đó khác nhau tùy thuộc vào liệu nút cha là phần tử DOM (có thể có con) hay văn bản DOM (có thể ' t): và do đó phương thức insertChildren có thể là ảo (hoặc trừu tượng) trong lớp cơ sở DomNode và được triển khai khác nhau trong mỗi lớp con DomElementDomText.Khi nào sử dụng thông tin loại thời gian chạy?

Một khả năng khác là cung cấp cho các cá thể một thuộc tính chung, có giá trị có thể được đọc: ví dụ: thuật toán có thể đọc thuộc tính nodeType của lớp cơ sở DomNode; hoặc cho một ví dụ khác, bạn có thể có các kiểu khác nhau (các lớp con) của gói mạng, chia sẻ một tiêu đề gói chung, và bạn có thể đọc tiêu đề gói để xem loại gói đó là gì.

tôi đã không sử dụng thông tin thời gian chạy kiểu nhiều, bao gồm:

  • Các isas từ khóa trong C#
  • downcasting
  • Phương pháp Object.GetType trong dot net
  • Nhà điều hành typeid trong C++

Khi tôi thêm thuật toán mới m phụ thuộc vào loại phân lớp, tôi có xu hướng thay vào đó để thêm một phương thức ảo mới vào phân cấp lớp.

Câu hỏi của tôi là, khi nào thích hợp để sử dụng thông tin kiểu thời gian chạy, thay vì các chức năng ảo?

Trả lời

5

Khi không còn cách nào khác. Các phương thức ảo luôn được ưa thích nhưng đôi khi chúng không thể được sử dụng. Có một vài lý do tại sao điều này có thể xảy ra nhưng phổ biến nhất là bạn không có mã nguồn của các lớp bạn muốn làm việc hoặc bạn không thể thay đổi chúng. Điều này thường xảy ra khi bạn làm việc với hệ thống cũ hoặc với thư viện thương mại nguồn đóng.

Trong .NET nó cũng có thể xảy ra mà bạn phải tải các hội đồng mới trên bay, như bổ trợ và bạn thường không có lớp cơ sở nhưng phải sử dụng một cái gì đó như gõ vịt.

+0

Lý do để nói rằng RTTI không được dùng nữa, phương pháp cuối cùng là gì? – ChrisW

+2

@ChrisW, Nó chỉ khó hiểu và chậm hơn nhiều để thực thi. Nó không phải là không được chấp nhận, nó chỉ là phương pháp khác là tốt hơn :) – vava

+0

Có rất ít lý do để nó được chậm hơn: RTTI có thể được lưu trữ trong lớp vtable, giống như một con trỏ hàm ảo. Tôi không chắc tại sao nó khó hiểu hơn, bởi vì theo cách kiểm tra RTTI là địa phương hơn: ví dụ nếu bạn thấy "if (foo is Foo)" thì bạn biết những gì đang được kiểm tra, mà không đi và nhìn vào các định nghĩa của các hàm ảo trong một số lớp con. – ChrisW

3

Trong C++, trong số một số trường hợp ít người biết đến khác (chủ yếu là đối phó với các lựa chọn thiết kế kém), RTTI là cách để thực hiện cái gọi là multi methods.

+0

Đó là 'một' cách, có; một cách khác là sử dụng http://en.wikipedia.org/wiki/Double_dispatch chỉ sử dụng các chức năng ảo: bạn sẽ thêm một hàm ảo mới cho mỗi lớp con của kiểu tham số. – ChrisW

+0

Nếu bạn nhìn vào liên kết tôi đã đăng, bạn sẽ thấy rằng, trên thực tế, dẫn đến "nhiều công văn", đó là sự tổng quát hóa của công văn kép. ':)' – sbi

0

dynamic_cast <>, nếu tôi nhớ chính xác, phụ thuộc vào RTTI. Một số giao diện bên ngoài tối nghĩa cũng có thể dựa vào RTTI khi một đối tượng được truyền qua một con trỏ void (vì bất kỳ lý do gì rằng có thể xảy ra).

Điều đó đang được nói, tôi đã không thấy typeof() trong tự nhiên trong 10 năm làm việc bảo trì C++. (May mắn thay.)

+1

typeof sẽ được thêm vào với C++ 0x làm từ khóa decltype (rất hữu ích, IMO).Nó không thuộc về thông tin kiểu thời gian chạy, là một cấu trúc biên dịch thời gian và một cách để sử dụng hiệu quả một số thông tin mà trình biên dịch C++ có, nhưng hiện tại chỉ được sử dụng trong các thông báo lỗi. – UncleBens

0

Bạn có thể tham khảo thêm hiệu quả C# cho trường hợp kiểm tra loại thời gian chạy là OK.

Mục 3.Sử dụng các thuật toán chung Sử dụng kiểm tra kiểu thời gian chạy

Bạn có thể dễ dàng sử dụng lại generics theo số chỉ định các tham số kiểu mới. Một phiên bản mới với loại mới thông số có nghĩa là một loại mới có chức năng tương tự .

Tất cả điều này là tuyệt vời, bởi vì bạn viết ít mã hơn. Tuy nhiên, đôi khi là các phương tiện chung chung khác không sử dụng số lợi thế cụ thể hơn, nhưng cấp trên, thuật toán rõ ràng. Quy tắc ngôn ngữ C# tính đến điều này. Tất cả những gì bạn cần là để nhận ra rằng thuật toán của bạn có thể hiệu quả hơn khi các thông số loại có khả năng lớn hơn và sau đó là viết mã cụ thể đó. Ngoài ra, tạo loại chung thứ hai là chỉ định các ràng buộc khác nhau không phải lúc nào cũng hoạt động. Phiên bản chung dựa trên loại biên dịch của đối tượng và không phải loại thời gian chạy. Nếu bạn không đạt được số , bạn có thể bỏ lỡ hiệu quả có thể có.

Ví dụ: giả sử bạn viết một lớp cung cấp điều tra thứ tự đảo ngược trên một chuỗi các mục được trình bày qua IEnumerable < T>. Để liệt kê ngược lại, bạn có thể lặp lại nó và sao chép các mục vào một bộ sưu tập trung gian với quyền truy cập chỉ mục như Danh sách < T> và liệt kê bộ sưu tập đó bằng cách sử dụng truy cập chỉ mục ngược. Nhưng nếu IEnumerable ban đầu của bạn là IList tại sao không tận dụng lợi thế của nó và cung cấp cách thực hiện nhiều hơn (mà không cần sao chép vào bộ sưu tập trung gian) để lặp lại các mục ngược. Vì vậy, về cơ bản nó là một đặc biệt chúng ta có thể tận dụng nhưng vẫn cung cấp cùng một hành vi (lặp lại chuỗi ngược).

Nhưng nói chung, bạn nên cẩn thận xem xét kiểm tra loại thời gian chạy và đảm bảo rằng nó không vi phạm Nguyên tắc thay thế Liskov.

1

Cấu trúc này ("là" và "như") rất quen thuộc đối với các nhà phát triển Delphi vì các trình xử lý sự kiện thường là các đối tượng downcast cho một tổ tiên chung. Ví dụ sự kiện OnClick chuyển chỉ argurment Người gửi: TObject bất kể loại đối tượng, cho dù đó là TButton, TListBox hay bất kỳ khác. Nếu bạn muốn biết thêm chi tiết về đối tượng này, bạn phải truy cập nó thông qua "as", nhưng để tránh ngoại lệ, bạn có thể kiểm tra nó bằng "is" trước đó. Downcasting này cho phép ràng buộc kiểu thiết kế của các đối tượng và các phương thức mà không thể thực hiện được với việc kiểm tra kiểu lớp nghiêm ngặt. Hãy tưởng tượng bạn muốn làm điều tương tự nếu người dùng nhấp vào Button hoặc ListBox, nhưng nếu họ cung cấp cho chúng tôi các nguyên mẫu khác nhau của các hàm, thì không thể ràng buộc chúng với cùng một quy trình.

Trong trường hợp tổng quát hơn, một đối tượng có thể gọi một hàm để thông báo rằng đối tượng ví dụ đã thay đổi. Nhưng trước đó nó rời khỏi điểm đến khả năng để biết anh ta "cá nhân" (thông qua như và là), nhưng không nhất thiết. Nó thực hiện điều này bằng cách chuyển tự như một tổ tiên chung nhất của tất cả các đối tượng (TObject trong trường hợp Delphi)

+0

Có, trình xử lý sự kiện là một ví dụ điển hình. Nói chung, bất kỳ lúc nào ứng dụng của bạn muốn lưu trữ một con trỏ tới một loại ứng dụng, sử dụng mã khung công tác không biết các loại ứng dụng của bạn; điều này bao gồm ví dụ bằng cách sử dụng thuộc tính 'object Tag' của nhiều kiểu khung net dot, và' void * lpParameter' được truyền cho hàm Win32 'CreateThread'. Bạn có thể lưu trữ nó một cách dễ dàng, nhưng sau đó bạn cần phải downcast khi bạn nhận được nó trở lại. – ChrisW

+0

@ChrisW: Tôi không nghĩ rằng bạn có thể 'dynamic_cast' từ một' void * '(ít nhất không phải trong C++). Trình biên dịch biết có một đối tượng kiểu đa hình tại địa chỉ được trỏ đến và bố cục dữ liệu nội bộ của nó là gì? – sbi

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