Đa hình được sử dụng trong thế giới thực như thế nào? [đóng cửa]


17

Tôi đang cố gắng hiểu làm thế nào Đa hình được sử dụng trong một dự án thực tế, nhưng tôi chỉ có thể tìm thấy ví dụ cổ điển (hoặc một cái gì đó tương tự) về việc có một Animallớp cha mẹ với một phương thức speak()và nhiều lớp con ghi đè phương thức này, và bây giờ bạn có thể gọi phương thức speak()trên bất kỳ đối tượng con nào, ví dụ:

Animal animal;

animal = dog;
animal.speak();

animal = cat;
animal.speak();



1
Các bộ sưu tập, mà bạn nhìn thấy và sử dụng mỗi ngày, bản thân nó đủ để hiểu đa hình là gì. Nhưng làm thế nào để sử dụng đa hình một cách hiệu quả trong việc giải quyết vấn đề là một kỹ năng bạn đạt được nhiều nhất bằng kinh nghiệm và không chỉ bằng cách thảo luận. Tiếp tục và có được bàn tay của bạn bẩn.
Durgadass S

Nếu bạn có một tập hợp các loại sẽ hỗ trợ một số loại giao diện tối thiểu (ví dụ: một bộ các đối tượng cần được vẽ), một giao diện thường phù hợp để che giấu sự khác biệt giữa các đối tượng khỏi cuộc gọi để vẽ nó. Ngoài ra, nếu bạn đang tạo (hoặc làm việc với) một API có các phương thức có thể phục vụ một đối tượng cơ sở và một số lượng đáng kể các kiểu thừa hưởng từ nó ít nhiều theo cùng một cách , đa hình có thể là cách tốt nhất để trừu tượng hóa sự khác biệt giữa Những loại đó.
jrh

Nói chung, nếu bạn thường xuyên thực hiện các phương thức quá tải để xử lý các loại khác nhau và mã tương tự nhau hoặc nếu bạn viết if(x is SomeType) DoSomething()thường xuyên, có thể đáng để sử dụng đa hình. Đối với tôi, đa hình là một quyết định tương tự như khi nào thực hiện một phương thức riêng biệt, nếu tôi thấy rằng tôi đã lặp lại mã một vài lần, tôi thường tái cấu trúc nó thành một phương thức và nếu tôi thấy rằng tôi if object is this type do thisthường xuyên tạo mã, thì đó có thể là giá trị tái cấu trúc và thêm một giao diện hoặc lớp.
18 giờ 34 phút

Câu trả lời:


35

Stream là ví dụ tuyệt vời của đa hình.

Luồng biểu thị một "chuỗi byte có thể đọc hoặc ghi". Nhưng trình tự này có thể đến từ tập tin, bộ nhớ hoặc nhiều loại kết nối mạng. Hoặc nó có thể đóng vai trò là trình trang trí, bao bọc luồng hiện có và biến đổi các byte theo một cách nào đó, như mã hóa hoặc nén.

Theo cách này, ứng dụng khách sử dụng Stream không cần quan tâm đến việc các byte đến từ đâu. Chỉ là họ có thể được đọc theo trình tự.

Một số người sẽ nói Streamlà ví dụ sai về đa hình, bởi vì nó định nghĩa nhiều "tính năng" mà người triển khai không hỗ trợ, như luồng mạng chỉ cho phép đọc hoặc viết, nhưng không phải cả hai cùng một lúc. Hoặc thiếu tìm kiếm. Nhưng đó chỉ là câu hỏi về sự phức tạp, vì Streamcó thể được chia thành nhiều phần có thể được thực hiện độc lập.


2
Trong các ngôn ngữ có nhiều thừa kế và ảo như C ++, ví dụ này thậm chí có thể chứng minh mẫu "kim cương đáng sợ" ... bằng cách lấy các lớp luồng đầu vào và đầu ra từ một lớp luồng cơ sở và mở rộng cả hai để tạo ra luồng I / O
gyre

2
@gyre Và làm tốt lắm, không có lý do gì để dreading mô hình kim cương. Cần phải nhận thức được sự đối nghịch trong viên kim cương và không gây ra xung đột tên với nó là điều quan trọng, và là một thách thức, và gây phiền nhiễu, và lý do để tránh mô hình kim cương ở nơi có thể thực hiện được ... nhưng đừng quá sợ hãi khi nó đi quá xa chỉ cần có một quy ước đặt tên có thể giải quyết các vấn đề.
KRyan

+1 Streams là ví dụ đa hình yêu thích mọi thời đại của tôi. Tôi thậm chí không cố gắng dạy cho mọi người mô hình 'động vật, động vật có vú, chó' hoàn hảo nữa, Streamlàm một công việc tốt hơn.
Pharap

@KRyan Tôi đã không bày tỏ suy nghĩ của mình bằng cách gọi nó là "viên kim cương đáng sợ", tôi vừa nghe nó được gọi như vậy. Tôi hoàn toàn đồng ý; Tôi nghĩ đó là thứ mà mọi nhà phát triển nên có thể quấn đầu và sử dụng một cách thích hợp.
gyre

@gyre Ồ, vâng, tôi thực sự hiểu điều đó; đó là lý do tại sao tôi bắt đầu với những người khác và, để chỉ ra rằng đó là một phần mở rộng của suy nghĩ của bạn, chứ không phải là một mâu thuẫn.
KRyan

7

Một ví dụ điển hình liên quan đến trò chơi sẽ là một lớp cơ sở Entity, cung cấp các thành viên phổ biến như draw()hoặc update().

Đối với một ví dụ định hướng dữ liệu thuần túy hơn, có thể có một lớp cơ sở Serializablecung cấp một phổ biến saveToStream()loadFromStream().


6

Có nhiều loại đa hình khác nhau, một trong những mối quan tâm thường là đa hình thời gian chạy / công văn động.

Một mô tả rất cao về đa hình thời gian chạy là một cuộc gọi phương thức thực hiện những điều khác nhau tùy thuộc vào kiểu thời gian chạy của các đối số của nó: chính đối tượng chịu trách nhiệm giải quyết một cuộc gọi phương thức. Điều này cho phép một lượng lớn tính linh hoạt.

Một trong những cách phổ biến nhất để sử dụng tính linh hoạt này là tiêm phụ thuộc , ví dụ để tôi có thể chuyển đổi giữa các triển khai khác nhau hoặc tiêm các đối tượng giả để thử nghiệm. Nếu tôi biết trước rằng sẽ chỉ có một số lượng hạn chế các lựa chọn có thể, tôi có thể cố gắng mã hóa chúng bằng các điều kiện, ví dụ:

void foo() {
  if (isTesting) {
    ... // do mock stuff
  } else {
    ... // do normal stuff
  }
}

Điều này làm cho mã khó theo dõi. Cách khác là giới thiệu một giao diện cho hoạt động foo đó và viết một triển khai bình thường và thực hiện giả của giao diện đó, và tiêm chích vào các triển khai mong muốn trong thời gian chạy. Căng tin phụ thuộc vào phòng ăn là một thuật ngữ phức tạp cho việc vượt qua đối tượng chính xác như là một đối số.

Là một ví dụ thực tế, tôi hiện đang làm việc với một vấn đề máy học tử tế. Tôi có một thuật toán đòi hỏi một mô hình dự đoán. Nhưng tôi muốn thử các thuật toán học máy khác nhau. Vì vậy, tôi xác định một giao diện. Tôi cần gì từ mô hình dự đoán của tôi? Cho một số mẫu đầu vào, dự đoán và lỗi của nó:

interface Model {
  def predict(sample) -> (prediction: float, std: float);
}

Thuật toán của tôi có một hàm xuất xưởng đào tạo một mô hình:

def my_algorithm(..., train_model: (observations) -> Model, ...) {
  ...
  Model model = train_model(observations);
  ...
  y, std = model.predict(x)
  ...
}

Bây giờ tôi có nhiều triển khai khác nhau của giao diện mô hình và có thể so sánh chúng với nhau. Một trong những triển khai này thực sự cần hai mô hình khác và kết hợp chúng thành một mô hình được tăng cường. Vì vậy, nhờ giao diện này:

  • thuật toán của tôi không cần biết về các mô hình cụ thể trước,
  • Tôi có thể dễ dàng trao đổi các mô hình, và
  • Tôi có rất nhiều sự linh hoạt trong việc thực hiện các mô hình của tôi.

Một trường hợp sử dụng cổ điển của đa hình là trong GUI. Trong một khung GUI như Java AWT / Swing /, có các thành phần khác nhau . Giao diện thành phần / lớp cơ sở mô tả các hành động như vẽ chính nó lên màn hình hoặc phản ứng với các nhấp chuột. Nhiều thành phần là các container quản lý các thành phần phụ. Làm thế nào một container như vậy có thể tự vẽ?

void paint(Graphics g) {
  super.paint(g);
  for (Component child : this.subComponents)
    child.paint(g);
}

Ở đây, container không cần biết về các loại chính xác của các thành phần con trước - miễn là chúng phù hợp với Componentgiao diện, container có thể chỉ cần gọi paint()phương thức đa hình . Điều này cho tôi tự do mở rộng hệ thống phân cấp lớp AWT với các thành phần mới tùy ý.

Có nhiều vấn đề định kỳ trong suốt quá trình phát triển phần mềm có thể được giải quyết bằng cách áp dụng đa hình như một kỹ thuật. Các cặp giải pháp vấn đề định kỳ này được gọi là các mẫu thiết kế và một số trong số chúng được thu thập trong cuốn sách cùng tên. Theo thuật ngữ của cuốn sách đó, mô hình học máy được tiêm của tôi sẽ là một chiến lược mà tôi sử dụng để định nghĩa một họ các thuật toán, gói gọn từng thuật toán và biến chúng thành hoán đổi cho nhau. Ví dụ Java-AWT trong đó một thành phần có thể chứa các thành phần phụ là một ví dụ về hỗn hợp .

Nhưng không phải mọi thiết kế đều cần sử dụng đa hình (ngoài việc cho phép tiêm phụ thuộc để thử nghiệm đơn vị, đây là trường hợp sử dụng thực sự tốt). Hầu hết các vấn đề là rất tĩnh. Kết quả là, các lớp và các phương thức thường không được sử dụng cho đa hình, mà chỉ đơn giản là các không gian tên thuận tiện và cho cú pháp gọi phương thức đẹp. Ví dụ, nhiều nhà phát triển thích các cuộc gọi phương thức như account.getBalance()trên một cuộc gọi chức năng phần lớn tương đương Account_getBalance(account). Đó là một cách tiếp cận hoàn toàn tốt, chỉ là nhiều cuộc gọi của Phương thức mà không liên quan gì đến đa hình.


6

Bạn thấy rất nhiều sự kế thừa và đa hình trong hầu hết các bộ công cụ UI.

Ví dụ, trong bộ công cụ UI của JavaFX, Buttonkế thừa từ ButtonBaseđó kế thừa từ Labeledđó kế thừa từ Controlđó kế thừa từ Regionđó kế thừa từ Parentđó kế thừa từ Nodeđó kế thừa từ đâu Object. Nhiều lớp ghi đè lên một số phương thức từ các phương thức trước.

Khi bạn muốn nút đó xuất hiện trên màn hình, sau đó bạn thêm nó vào a Pane, có thể chấp nhận mọi thứ thừa hưởng từ Nodekhi còn nhỏ. Nhưng làm thế nào để một Pane biết phải làm gì với Nút khi nó chỉ xem nó là một đối tượng Node chung? Đối tượng đó có thể là bất cứ điều gì. Cửa sổ có thể làm điều đó bởi vì Nút xác định lại các phương thức của Nút với bất kỳ logic cụ thể nào của nút. Khung chỉ gọi các phương thức được định nghĩa trong Node và để phần còn lại cho chính đối tượng. Đây là một ví dụ hoàn hảo về đa hình ứng dụng.

Bộ công cụ UI có ý nghĩa thế giới thực rất cao, khiến chúng trở nên hữu ích để dạy cho cả lý do học thuật và thực tiễn.

Tuy nhiên, bộ công cụ UI cũng có một nhược điểm đáng kể: Chúng có xu hướng rất lớn . Khi một kỹ sư phần mềm tân tiến cố gắng hiểu các hoạt động bên trong của một khung UI chung, họ sẽ thường gặp hơn một trăm lớp , hầu hết trong số họ phục vụ các mục đích rất bí truyền. "Cái quái ReadOnlyJavaBeanLongPropertyBuildergì vậy? Nó có quan trọng không? Tôi phải hiểu nó tốt cho cái gì không?" Người mới bắt đầu có thể dễ dàng bị lạc trong lỗ thỏ đó. Vì vậy, họ có thể chạy trốn trong khủng bố hoặc ở trên bề mặt nơi họ chỉ học cú pháp và cố gắng không suy nghĩ quá nhiều về những gì thực sự xảy ra dưới mui xe.


3

Mặc dù đã có những ví dụ hay ở đây, một ví dụ khác là thay thế động vật bằng thiết bị:

  • Devicecó thể powerOn(), powerOff(), setSleep()và lon getSerialNumber().
  • SensorDevicecó thể làm tất cả điều này, và cung cấp các chức năng đa hình như getMeasuredDimension(), getMeasure(), alertAt(threashhold)autoTest().
  • tất nhiên, getMeasure()sẽ không được thực hiện theo cách tương tự đối với cảm biến nhiệt độ, máy dò ánh sáng, máy dò âm thanh hoặc cảm biến thể tích. Và tất nhiên, mỗi cảm biến chuyên dụng hơn này có thể có sẵn một số chức năng bổ sung.

2

Trình bày là một ứng dụng rất phổ biến, có lẽ là ứng dụng phổ biến nhất là ToString (). Về cơ bản là Animal.Speak (): Bạn bảo một đối tượng tự hiển thị.

Nói chung hơn là bạn bảo một đối tượng "làm việc của nó". Hãy suy nghĩ Lưu, Tải, Khởi tạo, Loại bỏ, ProcessData, GetStatus.


2

Cách sử dụng đa hình thực tế đầu tiên của tôi là triển khai Heap trong java.

Tôi đã có lớp cơ sở với việc thực hiện các phương thức insert, removeTop trong đó sự khác biệt giữa max và min Heap sẽ chỉ là cách so sánh phương thức hoạt động.

abstract class Heap {  

 abstract boolean compare ( int x , int y );

 boolean insert(int x ) { ... }

 int removeTop() { ... }
}

Vì vậy, khi tôi muốn có MaxHeap và MinHeap, tôi chỉ có thể sử dụng tính kế thừa.

class MaxHeap extends Heap {

   MaxHeap(int maxSize) {super(maxSize);}

   @Override
   boolean compare(int x, int y) {
       return x>y; // x<y for minHeap
   }
}

1

Đây là một kịch bản thực tế cho đa hình bảng ứng dụng / cơ sở dữ liệu web :

Tôi sử dụng Ruby on Rails để phát triển các ứng dụng web và một điều mà rất nhiều dự án của tôi có điểm chung là khả năng tải lên các tệp (ảnh, PDF, v.v.). Vì vậy, ví dụ, một Userhình ảnh có thể có nhiều hình ảnh hồ sơ và Productcũng có thể có nhiều hình ảnh sản phẩm. Cả hai đều có hành vi tải lên và lưu trữ hình ảnh, cũng như thay đổi kích thước, tạo hình thu nhỏ, v.v. Để giữ DRY và chia sẻ hành vi cho Picture, chúng tôi muốn tạo Pictuređa hình để nó có thể thuộc về cả hai UserProduct.

Trong Rails tôi sẽ thiết kế các mô hình của mình như sau:

class Picture < ApplicationRecord
  belongs_to :imageable, polymorphic: true
end

class User < ApplicationRecord
  has_many :pictures, as: :imageable
end

class Product < ApplicationRecord
  has_many :pictures, as: :imageable
end

và di chuyển cơ sở dữ liệu để tạo picturesbảng:

class CreatePictures < ActiveRecord::Migration[5.0]
  def change
    create_table :pictures do |t|
      t.string  :name
      t.integer :imageable_id
      t.string  :imageable_type
      t.timestamps
    end

    add_index :pictures, [:imageable_type, :imageable_id]
  end
end

Các cột imageable_idimageable_typeđược sử dụng bởi Rails trong nội bộ. Về cơ bản, imageable_typegiữ tên của lớp ( "User", "Product", vv), và imageable_idlà id của hồ sơ liên quan. Vì vậy, imageable_type = "User"imageable_id = 1sẽ là kỷ lục trong usersbảng với id = 1.

Điều này cho phép chúng tôi thực hiện những việc như user.picturestruy cập ảnh của người dùng cũng như product.pictureslấy ảnh của sản phẩm. Sau đó, tất cả các hành vi liên quan đến hình ảnh được gói gọn trong Photolớp (và không phải là một lớp riêng biệt cho mỗi mô hình cần ảnh), vì vậy mọi thứ được giữ DRY.

Đọc thêm: Hiệp hội đa hình Rails .


0

Có nhiều thuật toán sắp xếp có sẵn như sắp xếp bong bóng, sắp xếp chèn, sắp xếp nhanh, sắp xếp heap, v.v. và chúng có độ phức tạp khác nhau và cái nào là tối ưu để sử dụng phụ thuộc vào các yếu tố khác nhau (ví dụ: kích thước của mảng)

Máy khách được cung cấp với giao diện sắp xếp chỉ quan tâm đến việc cung cấp mảng làm đầu vào và sau đó nhận mảng được sắp xếp. Trong thời gian chạy tùy thuộc vào các yếu tố nhất định thực hiện sắp xếp phù hợp có thể được sử dụng. Đây là một trong những ví dụ thực tế về nơi đa hình được sử dụng.

Những gì tôi mô tả ở trên là một ví dụ về đa hình thời gian chạy trong khi quá tải phương thức là một ví dụ về đa hình thời gian biên dịch trong đó trình biên dịch phụ thuộc vào các loại tham số i / p và o / p và số lượng tham số liên kết người gọi với phương thức đúng vào thời gian tuân thủ.

Hy vọng điều này làm rõ.

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.