深刻探討多態性及其在Java中的好處

多態是面向對象軟件的基本原理之一。該術語一般表示能夠具備多種形式的事物。在面向對象的方法中,多態使編寫具備後期綁定引用的程序成爲可能。儘管在Java中建立多態引用很容易,但其背後的概念對總體編程產生了更深遠的影響。本文結合在優銳課學習到的知識點,探討了有關多態性及其對面向對象編程的影響的一些複雜細節。java

多態參考:概述

多態引用是一個變量,能夠在不一樣的時間點引用不一樣類型的對象。它一般與它所引用的類兼容。 例如,在如下狀況下:編程

Employee employee;

 

'employee'是一個引用變量,能夠引用Employee類的實例。參考變量對參考對象的限定取決於其兼容性。這彷佛是惟一可靠的條件,但事實並不是如此,特別是在實現多態時。規則太嚴格了,可是經過結合「具備多種形式」的思想,多態性使得靈活性更高。這意味着多態引用保證了它能夠在不一樣的時間點引用不一樣類型的對象,而不是爲兼容性而徹底依賴。所以,若是能夠在一個時間點使用引用來調用方法,則能夠將其動態更改成指向另外一個對象,並在下一次調用其餘方法。經過提供引用變量的另外一個使用維度,這能夠利用靈活性。數組

當引用變量綁定到沒法在運行時更改的對象時,或者換句話說,方法調用與方法定義的綁定是在編譯時完成的,稱爲靜態綁定。若是綁定在運行時是可更改的,例如在多態引用的狀況下,綁定的決策僅在執行期間作出,則稱爲動態綁定或後期綁定。二者在面向對象程序設計中都有其用途,並不是一個都賽過另外一個。可是,在多態引用的狀況下,推遲的綁定承諾在靈活性方面使其比編譯時綁定更具優點,但另外一方面,這會下降性能開銷。可是,這在很大程度上是能夠接受的,而且在提升效率方面,開銷一般具備很小的吸引力。.架構

建立多態

在Java中,能夠經過兩種方式建立多態引用:使用繼承或使用接口。ide

繼承多態

引用變量引用類的實例。對於繼承層次結構,若是引用變量在層次結構樹中聲明爲父類類型,則引用對象能夠指向層次結構中任何類的實例。這意味着,在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派生派生;在這種狀況下,相反的關係(例如,對象是僱員)太牽強。this

繼承多態性:一個例子

讓咱們嘗試藉助示例來理解它。url

 

 1:從驅動程序類,公司派生的類spa

稱爲Company的驅動程序類建立一個僱員列表,並調用paySalary()方法。薪資類維護公司中不一樣類型員工的列表。請注意,該數組被聲明爲派生自Employee類(全部僱員子類的父級或超類)的引用變量的數組。結果,能夠用從Employee類的任何子類(例如CommissionEmployeeHourlyEmployee, 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,並構成了多態性的基礎。

總結

經過類繼承或經過接口實現多態性之間的差別是一個選擇問題。實際上,區別在於理解類和接口的屬性和特性。除了瞭解其性質外,沒有嚴格的規則來定義什麼時候使用。這超出了本文的範圍。可是,在多態中,這個想法既適合而且也有能力完成咱們想對它們進行的操做。就這樣。抽絲剝繭,細說架構那些事——【優銳課】

相關文章
相關標籤/搜索