多態是面向對象軟件的基本原理之一。該術語通常表示可以具有多種形式的事物。在面向對象的方法中,多態使編寫具有后期綁定引用的程序成為可能。盡管在Java中創建多態引用很容易,但其背后的概念對整體編程產生了更深遠的影響。本文結合在優銳課學習到的知識點,探討了有關多態性及其對面向對象編程的影響的一些復雜細節。
多態參考:概述
多態引用是一個變量,可以在不同的時間點引用不同類型的對象。它通常與它所引用的類兼容。 例如,在以下情況下:
Employee employee;
'employee'是一個引用變量,可以引用Employee類的實例。參考變量對參考對象的限定取決於其兼容性。這似乎是唯一可靠的條件,但事實並非如此,特別是在實現多態時。規則太嚴格了,但是通過結合“具有多種形式”的思想,多態性使得靈活性更高。這意味着多態引用保證了它可以在不同的時間點引用不同類型的對象,而不是為兼容性而完全依賴。因此,如果可以在一個時間點使用引用來調用方法,則可以將其動態更改為指向另一個對象,並在下一次調用其他方法。通過提供引用變量的另一個使用維度,這可以利用靈活性。
當引用變量綁定到無法在運行時更改的對象時,或者換句話說,方法調用與方法定義的綁定是在編譯時完成的,稱為靜態綁定。如果綁定在運行時是可更改的,例如在多態引用的情況下,綁定的決策僅在執行期間做出,則稱為動態綁定或后期綁定。兩者在面向對象程序設計中都有其用途,並非一個都勝過另一個。但是,在多態引用的情況下,推遲的綁定承諾在靈活性方面使其比編譯時綁定更具優勢,但另一方面,這會降低性能開銷。但是,這在很大程度上是可以接受的,並且在提高效率方面,開銷通常具有很小的吸引力。.
創建多態
在Java中,可以通過兩種方式創建多態引用:使用繼承或使用接口。
繼承多態
引用變量引用類的實例。對於繼承層次結構,如果引用變量在層次結構樹中聲明為父類類型,則引用對象可以指向層次結構中任何類的實例。這意味着,在Java中,因為Objectclass是所有類的父類或超類,或者換句話說,Java中的所有類實際上是Object類的子類隱式或顯式地是對象類的引用變量。對象類型可以引用Java中的任何類實例。這就是我們的意思。
1 Employee employee; 2 Object object; 3 employee = new Employee(); 4 object = employee; // This is a valid assignment
如果情況相反,則如下所示:
1 Employee employee; 2 Object object = new Object(); 3 employee = (Employee)object // Valid, but needs explicit cast
觀察到它需要顯式強制轉換;只有這樣,它才能成為有效的聲明。可以看出,這種反向分配對於在許多情況下出現問題的邊緣來說沒有多大用處。這是因為Object實例的功能與Employee引用變量預期的功能幾乎沒有關系。關系is-a可以從employee-is-an-object派生派生;在這種情況下,相反的關系(例如,對象是雇員)太牽強。
繼承多態性:一個例子
讓我們嘗試借助示例來理解它。
圖1:從驅動程序類,公司派生的類
稱為Company的驅動程序類創建一個雇員列表,並調用paySalary()方法。薪資類維護公司中不同類型員工的列表。請注意,該數組被聲明為派生自Employee類(所有雇員子類的父級或超類)的引用變量的數組。結果,可以用從Employee類的任何子類(例如CommissionEmployee, HourlyEmployee, SalariedEmployee)創建的對象引用填充數組。在paySalary()定義中,根據數組中的對象引用調用適當的salary()方法。因此,對salary()方法的調用是多態的,很明顯,每個類都有其自己版本的salary()方法。
Payroll類中的employee數組不代表特定類型的Employee。它用作可以指向任何類型的Employee子類引用的句柄。盡管繼承的類共享作為后代繼承的一些公共數據,但是它們具有各自的屬性集。
通過繼承實現多態:Java實現
這是該示例在Java中的快速實現。
1 package org.mano.example; 2 public class Company 3 { 4 public static void main( String[] args ) 5 { 6 Payroll payroll = new Payroll(); 7 payroll.paySalary(); 8 } 9 } 10 package org.mano.example; 11 import java.util.ArrayList; 12 import java.util.List; 13 public class Payroll { 14 private List<Employee> employees = 15 new ArrayList<>(); 16 public Payroll() { 17 employees.add(new 18 SalariedEmployee("Harry Potter", 19 "123-234-345",7800)); 20 employees.add(new 21 CommissionEmployee("Peter Parker", 22 "234-345-456",2345.67,0.15)); 23 employees.add(new 24 HourlyEmployee("Joker Poker", 25 "456-567-678",562.36,239.88)); 26 } 27 public void paySalary() { 28 for (Employee e: employees) { 29 System.out.println 30 ("----------------------------------------------------"); 31 System.out.println(e.toString()); 32 System.out.printf 33 ("Gross payment: $%,.2f\n",e.salary()); 34 System.out.println 35 ("----------------------------------------------------"); 36 } 37 } 38 } 39 40 package org.mano.example; 41 42 public abstract class Employee { 43 protected String name; 44 protected String ssn; 45 public Employee(String name, String ssn) { 46 this.name = name; 47 this.ssn = ssn; 48 } 49 public String getName() { 50 return name; 51 } 52 public void setName(String name) { 53 this.name = name; 54 } 55 public String getSsn() { 56 return ssn; 57 } 58 public void setSsn(String ssn) { 59 this.ssn = ssn; 60 } 61 @Override 62 public String toString() { 63 return String.format("%s\nSSN: %s", 64 getName(),getSsn()); 65 } 66 public abstract double salary(); 67 } 68 69 package org.mano.example; 70 public class SalariedEmployee extends Employee { 71 protected double basicSalary; 72 public SalariedEmployee(String name, String ssn, 73 double basicSalary) { 74 super(name, ssn); 75 setBasicSalary(basicSalary); 76 } 77 public double getBasicSalary() { 78 return basicSalary; 79 } 80 public void setBasicSalary(double basicSalary) { 81 if(basicSalary>= 0.0) 82 this.basicSalary = basicSalary; 83 else 84 throw new IllegalArgumentException("basic " + 85 "salary must be greater than 0.0"); 86 } 87 @Override 88 public double salary() { 89 eturn getBasicSalary(); 90 } 91 @Override 92 public String toString() { 93 return String.format("%s\nBasic Salary: $%,.2f", 94 super.toString(),getBasicSalary()); 95 } 96 } 97 98 package org.mano.example; 99 public class HourlyEmployee extends Employee { 100 protected double wage; 101 protected double hours; 102 public HourlyEmployee(String name, String ssn, 103 double wage, double hours) { 104 super (name, ssn); 105 setWage(wage); 106 setHours(hours); 107 } 108 public double getWage() { 109 return wage; 110 } 111 public void setWage(double wage) { 112 if(wage >= 0.0) 113 this.wage = wage; 114 else 115 throw new IllegalArgumentException("wage " + 116 "must be > 0.0"); 117 } 118 public double getHours() { 119 return hours; 120 } 121 public void setHours(double hours) { 122 if(hours >= 0.0) 123 this.hours = hours; 124 else 125 throw new IllegalArgumentException("hours " + 126 "must be > 0.0"); 127 } 128 @Override 129 public double salary() { 130 return getHours() * getWage(); 131 } 132 @Override 133 public String toString() { 134 return String.format("%s\nWage: $%, 135 .2f\nHours worked: %,.2f", 136 super.toString(),getWage(),getHours()); 137 } 138 } 139 140 package org.mano.example; 141 public class CommissionEmployee extends Employee { 142 protected double sales; 143 protected double commission; 144 public CommissionEmployee(String name, String ssn, 145 double sales, double commission) { 146 super(name, ssn); 147 setSales(sales); 148 setCommission(commission); 149 } 150 public double getSales() { 151 return sales; 152 } 153 public void setSales(double sales) { 154 if(sales >=0.0) 155 this.sales = sales; 156 else 157 throw new IllegalArgumentException("Sales " + 158 "must be >= 0.0"); 159 } 160 public double getCommission() { 161 return commission; 162 } 163 public void setCommission(double commission) { 164 if(commission > 0.0 && commission < 1.0) 165 this.commission = commission; 166 else 167 throw new IllegalArgumentException("Commission " + 168 "must be between 0.0 and 1.0"); 169 } 170 @Override 171 public double salary() { 172 return getCommission() * getSales(); 173 } 174 @Override 175 public String toString() { 176 return String.format("%s\nSales: %, 177 .2f\nCommission: %,.2f", 178 super.toString(),getSales(),getCommission()); 179 } 180 }
接口多態
接口的多態性與前面的示例非常相似,不同之處在於,這里的多態性規則是根據Java接口指定的規范進行的。接口名稱可以用作引用變量,就像我們對上面的類名稱所做的那樣。它引用實現該接口的任何類的任何對象。這是一個例子。
1 package org.mano.example; 2 public interface Player { 3 public enum STATUS{PLAY,PAUSE,STOP}; 4 public void play(); 5 public void stop(); 6 public void pause(); 7 } 8 9 package org.mano.example; 10 public class VideoPlayer implements Player { 11 private STATUS currentStatus = STATUS.STOP; 12 @Override 13 public void play() { 14 if(currentStatus == STATUS.STOP || 15 currentStatus == STATUS.PAUSE) { 16 currentStatus = STATUS.PLAY; 17 System.out.println("Playing Video..."); 18 } 19 else 20 System.out.println("I am ON playing man!"); 21 } 22 @Override 23 public voidstop() { 24 if(currentStatus == STATUS.PLAY || 25 currentStatus == STATUS.PAUSE) { 26 currentStatus = STATUS.STOP; 27 System.out.println("Video play stopped."); 28 } 29 else 30 System.out.println("Do you want me to go fishing?"); 31 } 32 @Override 33 public void pause() { 34 if(currentStatus == STATUS.PLAY) { 35 currentStatus = STATUS.PAUSE; 36 System.out.println("Video play paused."); 37 } 38 else 39 System.out.println("I'm a statue. You froze me 40 already!"); 41 } 42 } 43 44 package org.mano.example; 45 public class AudioPlayer implements Player { 46 private STATUS currentStatus = STATUS.STOP; 47 @Override 48 public void play() { 49 if(currentStatus == STATUS.STOP || 50 currentStatus == STATUS.PAUSE) { 51 currentStatus = STATUS.PLAY; 52 System.out.println("Playing Audio..."); 53 } 54 else 55 System.out.println("I am ON playing man!"); 56 } 57 @Override 58 public void stop() { 59 if(currentStatus == STATUS.PLAY || 60 currentStatus == STATUS.PAUSE) { 61 currentStatus = STATUS.STOP; 62 System.out.println("Audio play stopped."); 63 } 64 else 65 System.out.println("Do you want me to go fishing?"); 66 } 67 @Override 68 public void pause() { 69 if(currentStatus == STATUS.PLAY) { 70 currentStatus = STATUS.PAUSE; 71 System.out.println("Audio play paused."); 72 } 73 else 74 System.out.println("I'm a statue. You froze me 75 already!"); 76 } 77 } 78 79 package org.mano.example; 80 public class PlayerApp { 81 public static void main(String[] args) { 82 Player player= new VideoPlayer(); 83 player.play(); 84 player.pause(); 85 player.stop(); 86 player= new AudioPlayer(); 87 player.play(); 88 player.pause(); 89 player.stop(); 90 } 91 }
請注意,在PlayerApp中,我們已經使用接口Player來聲明對象引用變量。引用變量player可以引用實現Player接口的任何類的任何對象。為了證明這一點,我們在這里使用了同一播放器變量來引用VideoPlayer對象和AudioPlayer對象。運行時調用的方法是特定於其引用的類對象的方法。實現接口的類與接口本身之間的關系是父子關系,正如我們在帶有繼承的多態示例中所看到的。它也是一個is-arelationship,並構成了多態性的基礎。
總結
通過類繼承或通過接口實現多態性之間的差異是一個選擇問題。實際上,區別在於理解類和接口的屬性和特性。除了了解其性質外,沒有嚴格的規則來定義何時使用。這超出了本文的范圍。但是,在多態中,這個想法既適合並且也有能力完成我們想對它們進行的操作。就這樣。抽絲剝繭,細說架構那些事——【優銳課】