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


多態是面向對象軟件的基本原理之一。該術語通常表示可以具有多種形式的事物。在面向對象的方法中,多態使編寫具有后期綁定引用的程序成為可能。盡管在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類的任何子類(例如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,並構成了多態性的基礎。

總結

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM