Java抽象– ULTIMATE教程(PDF下载)
編者注 :在本文中,我們提供了Java教程中的全面抽象。 抽象發生在類級別的設計中,目的是隱藏實現API /設計/系統提供的功能的方式的實現復雜性,從某種意義上講簡化了訪問底層實現的“接口”。 此過程可以在越來越“更高”的抽象層次(層)上重復進行,從而可以在較大的層上構建大型系統而不會增加代碼和理解的復雜性。
目錄
1.簡介 2.接口1.簡介
在本教程中,我們將介紹Java中的抽象,并使用接口,抽象類和具體類定義一個簡單的薪資系統。
Java有兩種抽象級別- 接口 (用于定義預期的行為)和抽象類 (用于定義不完整的功能)。
現在,我們將詳細介紹這兩種不同類型的抽象。
2.接口
接口就像合同。 承諾提供某些行為,并且所有實現接口保證的類也將實現那些行為。 為了定義預期的行為,接口列出了許多方法簽名。 任何使用該接口的類都可以依賴那些在實現該接口的運行時類中實現的方法。 這樣,使用該界面的任何人都可以知道將提供什么功能,而不必擔心該功能將如何實現。 客戶端看不到實現細節,這是抽象的關鍵好處。
定義接口
您可以使用關鍵字interface定義一個接口:
public interface MyInterface {void methodA();int methodB();String methodC(double x, double y);}在這里,我們看到一個定義為MyInterface的接口,請注意,對接口應該使用與對類相同的大小寫約定。 MyInterface定義了3種方法,每種方法具有不同的返回類型和參數。 您可以看到這些方法都沒有主體。 當使用接口時,我們只對定義預期的行為感興趣,而與實現無關。 注意:Java 8引入了為接口方法創建默認實現的功能,但是在本教程中我們將不介紹該功能。
接口還可以通過使用成員變量來包含狀態數據:
public interface MyInterfaceWithState {int someNumber;void methodA();}默認情況下,接口中的所有方法都是公共的,實際上您不能在具有非public訪問級別的接口中創建方法。
實施接口
現在,我們定義了一個我們要創建的類的接口,該類將提供已定義行為的實現細節。 為此,我們編寫了一個新類并使用implements關鍵字告訴編譯器該類應實現什么接口。
public class MyClass implements MyInterface {public void methodA() {System.out.println("Method A called!");}public int methodB() {return 42;}public String methodC(double x, double y) {return "x = " + x + ", y = " y;}}我們采用了在MyInterface定義的方法簽名,并為它們提供了實現它們的主體。 我們只是在實現中做了一些任意的愚蠢之舉,但必須注意的是,只要它們滿足方法簽名,我們就可以在這些主體中做任何事情。 我們還可以根據需要創建盡可能多的實現類,每個實現類都具有MyInterface方法的不同實現體。
我們在MyClass實現了MyInterface中的所有方法,如果我們未能實現它們中的任何一個,則編譯器將給出一個錯誤。 這是因為MyClass實現MyInterface的事實意味著MyClass保證為MyInterface中的每個方法提供實現。 這樣,使用該接口的任何客戶端都可以依靠這樣的事實:在運行時,可以保證要調用的方法已經實現。
使用介面
要從客戶端調用接口的方法,我們只需要使用點(。)運算符,就像使用類的方法一樣:
MyInterface object1 = new MyClass(); object1.methodA(); // Guaranteed to work我們在上面看到了一些不尋常的東西,而不是像MyClass object1 = new MyClass();這樣的東西MyClass object1 = new MyClass(); (這完全可以接受),我們將object1聲明為MyInterface類型。 之所以可行,是因為MyClass是MyInterface的實現,無論何時我們要調用MyInterface中定義的方法,我們都知道MyClass將提供實現。 object1是對實現MyInterface的任何運行時對象的引用 ,在這種情況下,它是MyClass的實例。 如果我們嘗試做MyInterface object1 = new MyInterface()我們將得到一個編譯器錯誤,因為您無法實例化一個接口,這是有道理的,因為該接口中沒有實現細節,也沒有執行代碼。
當我們調用object1.methodA()我們正在執行MyClass中定義的方法主體,因為對象1的運行時類型是MyClass,即使引用的類型是MyInterface。 我們只能在MyInterface中定義的object1上調用方法,出于所有目的和目的,即使運行時類型為MyClass,我們也可以將object1稱為MyInterface類型。 實際上,如果MyClass定義了另一個名為methodD()方法,我們將無法在object1上調用它,因為編譯器僅知道object1是對MyInterface的引用,而不是它專門是MyClass。
這個重要的區別使我們能夠為接口創建不同的實現類,而不必擔心在運行時調用哪個特定的實現類。
使用以下界面:
public interface OneMethodInterface {void oneMethod();}它定義了一個不帶參數的void方法。
讓我們實現它:
public class ClassA implements OneMethodInterface {public void oneMethod() {System.out.println("Runtime type is ClassA.");} }我們可以像以前一樣在客戶端中使用它:
OneMethodInterface myObject = new ClassA(); myObject.oneMethod();輸出:
Runtime type is ClassA.現在讓我們為OneMethodInterface做一個不同的實現:
public class ClassB implements OneMethodInterface {public void oneMethod() {System.out.println("The runtime type of this class is ClassB."); } }并修改上面的代碼:
OneMethodInterface myObject = new ClassA(); myObject.oneMethod(); myObject = new ClassB(); myObject.oneMethod();輸出:
Runtime type is ClassA. The runtime type of this class is ClassB.我們已經成功地使用相同的Reference( myObject )來引用兩種不同運行時類型的實例。 實際的實現對編譯器而言完全不重要,它只是在乎OneMethodInterface是否以任何方式和以任何方式實現。 就編譯器而言,myObject是OneMethodInterface,并且oneMethod()可用,即使oneMethod()其重新分配給不同類類型的其他實例對象也是如此。 這種提供多種運行時類型并在運行時而不是編譯時解析的功能稱為多態性 。
接口定義行為時沒有任何實現細節(忽略Java 8),實現類定義了它們定義的類的所有實現細節,但是如果我們希望將這兩個概念混合使用會怎樣? 如果要在同一位置混合一些行為定義和一些實現,則可以使用抽象類。
3.抽象類
抽象類就像一個不完整的藍圖,它定義了該類的一些實現細節,而另一些則保留為稍后要實現的簡單行為定義。
想象一下一個房子的藍圖,其中的房子被完全畫進去了,但是有一個大的空曠的正方形可以停放車庫。 我們知道會有車庫,但我們不知道它會是什么樣。 其他人將需要復制我們的藍圖并在車庫中繪制。 實際上,幾個不同的人可能會復制我們的藍圖并繪制不同類型的車庫。 使用這些藍圖建造的房屋都是我們房屋的可識別變體。 前門,房間布局和窗戶將完全相同,但是車庫將完全不同。
就像我們抽象類上面的藍圖將完全定義一些方法一樣,這些方法實現在抽象類的所有實現中都是相同的。 抽象類將僅為其他方法定義簽名,其方式與接口的定義大致相同。 這些方法的方法實現在實現類中會有所不同。 抽象類的實現類通常稱為具體類。 由于具體類和抽象類之間的繼承關系,我們通常說具體類擴展了抽象類,而不是像我們所說的那樣通過接口實現它。
就像使用接口一樣,任何客戶端代碼都知道,如果具體類正在擴展抽象類,則具體類將保證為抽象類的抽象方法提供方法體(抽象類為非抽象方法提供了它自己的方法體。課程)。
同樣,就像接口一樣,給定抽象類可以有幾個不同的具體類,每個類可以在滿足抽象類協定的同時為抽象類的抽象方法定義非常不同的行為。 實施細節對客戶端隱藏。
定義抽象類
關鍵字abstract用于將類及其方法定義為abstract。
public abstract class MyAbstractClass {protected int someNumber = 0;public void increment() {someNumber++;}public abstract void doSomethingWithNumber();}在這里,我們定義了一個名為MyAbstractClass的抽象類,該類包含一個整數,并提供了遞增該整數的方法。 我們還定義了一個名為doSomethingWithNumber()的抽象方法。 我們尚不知道此方法會做什么,它將在擴展MyAbstractClass任何具體類中MyAbstractClass 。 doSomethingWithNumber()沒有方法主體,因為它是抽象的。
在接口中,默認情況下所有方法都是公共的,但是抽象方法的范圍可以是公共的,打包的或受保護的。
您可以看到在此抽象類中,increment()中的某些行為實現與doSomethingWithNumber()中的某些行為定義混合在一起。 抽象類將某種實現與某種定義混合在一起。 擴展Abstract類的具體類將重用increment()的實現,同時保證提供自己的doSomethingWithNumber()的實現。
擴展抽象類
現在,我們已經創建了一個抽象類,讓我們對其進行具體實現。 我們通過在本身不是抽象的類中使用extend關鍵字,從抽象類中進行具體實現。
public class MyConcreteClass extends MyAbstractClass {public void sayHello() {System.out.println("Hello there!"); }public void doSomethingWithNumber() {System.out.println("The number is " + someNumber); }}我們創建了一個名為MyConcreteClass的具體類,并擴展了MyAbstractClass 。 我們只需要提供抽象方法doSomethingWithNumber()的實現,因為我們繼承了MyAbstractClass的非私有成員變量和方法。 如果有任何客戶端在MyConcreteClass上調用increment() ,則將執行MyAbstractClass中定義的實現。 我們還創建了一個名為sayHello()的新方法,該方法是MyConcreteClass所獨有的,其他實現MyAbstractClass的具體類都無法使用。
我們還可以使用不實現doSomethingWithNumber另一個抽象類來擴展MyAbstractClass ,這意味著必須定義另一個具體的類,該類將擴展這個新類以便實現doSomethingWithNumber() 。
public abstract class MyOtherAbstractClass extends MyAbstractClass {public void sayHello() {System.out.println("Hello there!"); } }在這里,我們不必對doSomethingWithNumber()進行任何引用,只要為MyOtherAbstractClass創建一個具體的類,我們便會為doSomethingWithNumber()提供實現。
最后,抽象類本身可以實現接口。 由于抽象類本身無法實例化,因此不必為所有(或任何)接口方法提供實現。 如果抽象類未提供接口方法的實現,則擴展抽象類的具體類將必須提供實現。
public abstract MyImplementingAbstractClass implements MyInterface {public void methodA() {System.out.println("Method A has been implemented in this abstract class");} }在這里,我們看到MyImplementingAbstractClass實現MyInterface,但僅提供methodA()的實現。 如果任何具體的類擴展了MyImplementingAbstractClass,它將必須提供MyInterface中定義的methodB()和methodC()的實現。
使用抽象類
就像接口和常規類一樣,要使用抽象(。)運算符來調用抽象類的方法。
MyAbstractClass object1 = new MyConcreteClass(); object1.increment(); object1.doSomethingWithNumber();再次,我們看到object1是對實例的引用,該實例為MyAbstractClass提供了具體的實現,并且該實例的運行時類型為MyConcreteClass。 出于所有目的,編譯器將object1視為MyAbstractClass實例。 如果嘗試調用MyConcreteClass中定義的sayHello()方法,則會收到編譯器錯誤。 通過object1,此方法對編譯器不可見 ,因為object1是MyAbstractClass引用。 object1唯一提供的保證是它將具有MyAbstractClass中定義的方法的實現,運行時類型提供的任何其他方法都不可見。
object1.sayHello(); // compiler error與接口一樣,我們可以提供不同的運行時類型,并通過相同的引用使用它們。
讓我們定義一個新的抽象類
public abstract class TwoMethodAbstractClass {public void oneMethod() {System.out.prinln("oneMethod is implemented in TwoMethodAbstractClass.");}public abstract void twoMethod(); }它定義了一個實現的方法和另一個抽象的方法。
讓我們用一個具體的類來擴展它
public class ConcreteClassA extends TwoMethodAbstractClass {public void twoMethod() {System.out.println("twoMethod is implemented in ConcreteClassA."); } }我們可以像以前一樣在客戶端中使用它:
TwoMethodAbstractClass myObject = new ConcreteClassA(); myObject.oneMethod(); myObject.twoMethod();輸出:
oneMethod is implemented in TwoMethodAbstractClass. twoMethod is implemented in ConcreteClassA.現在讓我們做一個擴展TwoMethodClass的不同的具體類:
public class ConcreteClassB extends TwoMethodAbstractClass {public void twoMethod() {System.out.println("ConcreteClassB implements its own twoMethod.");} }并修改上面的代碼:
TwoMethodAbstractClass myObject = new ConcreteClassA(); myObject.oneMethod(); myObject.twoMethod(); myObject = new ConcreteClassB(); myObject.oneMethod(); myObject.twoMethod();輸出:
oneMethod is implemented in TwoMethodAbstractClass. twoMethod is implemented in ConcreteClassA. oneMethod is implemented in TwoMethodAbstractClass. ConcreteClassB implements its own twoMethod.我們使用相同的引用(myObject)來引用兩種不同的運行時類型的實例。 當myObject的運行時類型為ConcreteClassA時,它將使用ConcreteClassA中的twoMethod的實現。 當myObject的運行時類型為ConcreteClassB時,它將使用ConcreteClassB中的twoMethod的實現。 在這兩種情況下,都使用TwoMethodAbstractClass中的oneMethod的通用實現。
抽象類用于定義常見行為,同時提供合同或承諾以后可以從具體類中獲得其他行為。 這使我們能夠建立對象模型,在其中可以重用通用功能并捕獲不同具體類中的不同需求。
4.一個可行的例子–付款系統
我們被要求為公司建立付款系統。 我們知道公司有不同類型的員工,可以通過不同的方式獲得報酬。 薪水和傭金。 該公司的老板知道他的需求將會改變,并且該系統可能會在以后更改,以適應將要接收付款的不同類型的實體。
收款人界面
讓我們開始考慮員工。 他們必須收到付款,但我們也知道以后可能需要讓其他實體也收到付款。 讓我們創建一個接口Payee,該接口將定義我們期望可以接收付款的實體所采取的行為。
public interface Payee {String name();Double grossPayment();Integer bankAccount(); }在這里,我們有一個Payee界面,可以保證三種行為: 提供收款人姓名的能力,提供應支付的總付款額的能力以及提供應將資金存入的銀行帳號的能力。
付款系統
現在我們定義了一個收款人,讓我們編寫一些使用它的代碼。 我們將創建一個PaymentSystem類,該類維護一個收款人列表,按需將在其間循環,并將請求的金額轉入相應的銀行帳戶。
public class PaymentSystem {private List<Payee> payees;public PaymentSystem() {payees = new ArrayList<>();}public void addPayee(Payee payee) {if (!payees.contains(payee)) {payees.add(payee);}}public void processPayments() {for (Payee payee : payees) {Double grossPayment = payee.grossPayment();System.out.println("Paying to " + payee.name());System.out.println("\tGross\t" + grossPayment);System.out.println("\tTransferred to Account: " + payee.bankAccount());}} }你可以看到,PaymentSystem是完全不可知的運行時類型的收款人的,它并不關心,沒有關心。 它知道,無論正在處理的收款人是哪種運行時類型,都可以保證實現name() , grossPayment()和bankAccount() 。 有了這些知識,只需在所有收款人之間執行for循環并使用這些方法處理其付款即可。
員工班
老板告訴我們,他有兩種不同類型的員工-薪水員工和特級員工。 受薪雇員的基本工資不會改變,而受雇雇員的基本工資不會改變,并且可以獲得成功銷售的銷售傭金。
4.3.1 SalaryEmployee類
讓我們從“受薪雇員”課程開始。 它應該實現“收款人”界面,以便支付系統可以使用它。
讓我們使用代碼之外的類圖來表示更改。 在類圖中,虛線箭頭表示“實現”關系,實線箭頭表示“擴展”關系。
類圖
圖1
碼
public class SalaryEmployee implements Payee {private String name;private Integer bankAccount;protected Double grossWage;public SalaryEmployee(String name, Integer bankAccount, Double grossWage) {this.name = name;this.bankAccount = bankAccount;this.grossWage = grossWage;}public Integer bankAccount() {return bankAccount;}public String name() {return name;}public Double grossPayment() {return grossWage;} }4.3.2 CommissionEmployee類
現在讓我們創建一個CommissionEmployee類。 該類與具有支付傭金能力的SalaryEmployee非常相似。
類圖
圖2
碼
public class CommissionEmployee implements Payee {private String name;private Integer bankAccount;protected Double grossWage;private Double grossCommission = 0.0;public CommissionEmployee(String name, Integer bankAccount, Double grossWage) {this.name = name;this.bankAccount = bankAccount;this.grossWage = grossWage;}public Integer bankAccount() {return bankAccount;}public String name() {return name;}public Double grossPayment() {return grossWage + doCurrentCommission();}private Double doCurrentCommission() {Double commission = grossCommission;grossCommission = 0.0;return commission;}public void giveCommission(Double amount) {grossCommission += amount;} }如您所見,SalaryEmployee和CommissionEmployee之間有很多代碼重復。 實際上,唯一不同的是GrossPayment的計算,該計算使用CommissionEmployee中的傭金值。 其中一些功能相同,而另一些不同。 這看起來像是抽象類的不錯候選人。
4.3.3員工抽象類
讓我們將名稱和銀行帳戶功能抽象為Employee抽象類。 然后,SalaryEmployee和CommissionEmployee可以擴展此抽象類,并為grossPayment()提供不同的實現。
類圖
圖3
碼
public abstract class Employee implements Payee {private String name;private Integer bankAccount;protected Double grossWage;public Employee(String name, Integer bankAccount, Double grossWage) {this.name = name;this.bankAccount = bankAccount;this.grossWage = grossWage;}public String name() {return name;}public Integer bankAccount() {return bankAccount;} }請注意,由于Employee是抽象的,因此Employee不必實現在Payee中定義的grossPayment()方法。
現在,我們重寫兩個Employee類:
public class SalaryEmployee extends Employee {public SalaryEmployee(String name, Integer bankAccount, Double grossWage) {super(name, bankAccount, grossWage);}public Double grossPayment() {return grossWage;} }public class CommissionEmployee extends Employee {private Double grossCommission = 0.0;public CommissionEmployee(String name, Integer bankAccount, Double grossWage) {super(name, bankAccount, grossWage);}public Double grossPayment() {return grossWage + doCurrentCommission();}private Double doCurrentCommission() {Double commission = grossCommission;grossCommission = 0.0;return commission;}public void giveCommission(Double amount) {grossCommission += amount;} }更整潔了!
應用程序
讓我們嘗試在應用程序中使用新的Employee類型。 我們將創建一個應用程序類,該應用程序類通過創建支付系統,一些員工并模擬一周的工作來初始化系統。
4.4.1 PaymentApplication類
應用程序類如下所示:
public class PaymentApplication {public static void main(final String... args) {// InitializationPaymentSystem paymentSystem = new PaymentSystem();CommissionEmployee johnSmith = new CommissionEmployee("John Smith", 1111, 300.0);paymentSystem.addPayee(johnSmith);CommissionEmployee paulJones = new CommissionEmployee("Paul Jones", 2222, 350.0);paymentSystem.addPayee(paulJones);SalaryEmployee maryBrown = new SalaryEmployee("Mary Brown", 3333, 500.0);paymentSystem.addPayee(maryBrown);SalaryEmployee susanWhite = new SalaryEmployee("Susan White", 4444, 470.0);paymentSystem.addPayee(susanWhite);// Simulate WeekjohnSmith.giveCommission(40.0);johnSmith.giveCommission(35.0);johnSmith.giveCommission(45.0);paulJones.giveCommission(45.0);paulJones.giveCommission(51.0);paulJones.giveCommission(23.0);paulJones.giveCommission(14.5);paulJones.giveCommission(57.3);// Process Weekly PaymentpaymentSystem.processPayments();}我們創建了兩個委托雇員和兩個受薪雇員,每個雇員都有自己的姓名,基本工資和銀行帳號。 我們將每個員工加載到我們創建的支付系統中。 然后,我們通過給兩名委托的雇員提供傭金來模擬一周,然后要求付款系統處理一周中的所有付款。
輸出:
Paying to John SmithGross 420.0Transferred to Account: 1111 Paying to Paul JonesGross 540.8Transferred to Account: 2222 Paying to Mary BrownGross 500.0Transferred to Account: 3333 Paying to Susan WhiteGross 470.0Transferred to Account: 4444處理紅利
到目前為止,老板對這個系統非常滿意,但是他告訴我們,為了激勵他的員工,他希望有能力給他們每周的百分比獎金。 他告訴我們,由于受托員工的薪水通常較低,因此我們應該給他們1.5倍的獎金乘數,以提高他們的獎金百分比。 獎金應反映在每位員工的總工資中。
4.5.1員工獎金類別
讓我們向員工添加一個字段以捕獲給定的任何獎金,使用受保護的方法來返回并重置獎金,并使用抽象方法來提供獎金。 doBonus()方法受到保護,以便具體的Employee類可以訪問它。 GiveBonus方法是抽象的,因為它將對受薪雇員和委托雇員實施不同的方法。
public abstract class Employee implements Payee {private String name;private Integer bankAccount;protected Double currentBonus;protected Double grossWage;public Employee(String name, Integer bankAccount, Double grossWage) {this.name = name;this.bankAccount = bankAccount;this.grossWage = grossWage;currentBonus = 0.0;}public String name() {return name;}public Integer bankAccount() {return bankAccount;}public abstract void giveBonus(Double percentage);protected Double doCurrentBonus() {Double bonus = currentBonus;currentBonus = 0.0;return bonus;} }對SalaryEmployee的更新:
public class SalaryEmployee extends Employee {public SalaryEmployee(String name, Integer bankAccount, Double grossWage) {super(name, bankAccount, grossWage);}public void giveBonus(Double percentage) {currentBonus += grossWage * (percentage/100.0);}public Double grossPayment() {return grossWage + doCurrentBonus();} }對CommissionEmployee的更新:
public class CommissionEmployee extends Employee {private static final Double bonusMultiplier = 1.5;private Double grossCommission = 0.0;public CommissionEmployee(String name, Integer bankAccount, Double grossWage) {super(name, bankAccount, grossWage);}public void giveBonus(Double percentage) {currentBonus += grossWage * (percentage/100.0) * bonusMultiplier;}public Double grossPayment() {return grossWage + doCurrentBonus() + doCurrentCommission();}private Double doCurrentCommission() {Double commission = grossCommission;grossCommission = 0.0;return commission;}public void giveCommission(Double amount) {grossCommission += amount;} }我們可以看到兩個類在實現GiveBonus()方面有所不同– CommissionEmployee使用獎金乘數。 我們還可以看到,在計算總付款時,這兩個類都使用了在Employee中定義的受保護的doCurrentBonus()方法。
讓我們更新應用程序,以模擬向員工支付每周獎金的方法:
public class PaymentApplication {public static void main(final String... args) {// InitializationPaymentSystem paymentSystem = new PaymentSystemV1();CommissionEmployee johnSmith = new CommissionEmployee("John Smith", 1111, 300.0);paymentSystem.addPayee(johnSmith);CommissionEmployee paulJones = new CommissionEmployee("Paul Jones", 2222, 350.0);paymentSystem.addPayee(paulJones);SalaryEmployee maryBrown = new SalaryEmployee("Mary Brown", 3333, 500.0);paymentSystem.addPayee(maryBrown);SalaryEmployee susanWhite = new SalaryEmployee("Susan White", 4444, 470.0);paymentSystem.addPayee(susanWhite);// Simulate WeekjohnSmith.giveCommission(40.0);johnSmith.giveCommission(35.0);johnSmith.giveCommission(45.0);johnSmith.giveBonus(5.0);paulJones.giveCommission(45.0);paulJones.giveCommission(51.0);paulJones.giveCommission(23.0);paulJones.giveCommission(14.5);paulJones.giveCommission(57.3);paulJones.giveBonus(6.5);maryBrown.giveBonus(3.0);susanWhite.giveBonus(7.5);// Process Weekly PaymentpaymentSystem.processPayments();} }輸出:
Paying to John SmithGross 442.5Transferred to Account: 1111 Paying to Paul JonesGross 574.925Transferred to Account: 2222 Paying to Mary BrownGross 515.0Transferred to Account: 3333 Paying to Susan WhiteGross 505.25Transferred to Account: 4444毛額現在反映了支付給員工的獎金。
承包公司
老板對支付系統很滿意,但是他想到了需要支付的其他人。 他將不時聘請承包公司的服務。 顯然,這些公司沒有工資,也沒有支付任何獎金。 他們可以得到幾筆一次性付款,在付款系統處理時,應向其支付所有累計付款,并結清其余額。
承包公司類
ContractingCompany類應實現Payee,以便付款系統可以使用它。 它應該有一種支付服務的方法,該方法將通過累計總數進行跟蹤并用于支付。
類圖
圖4
碼
public class ContractingCompany implements Payee {private String name;private Integer bankAccount;private Double currentBalance;public ContractingCompany(String name, Integer bankAccount) {this.name = name;this.bankAccount = bankAccount;currentBalance = 0.0;}public String name() {return name;}public Double grossPayment() {return doPayment();}private Double doPayment() {Double balance = currentBalance;currentBalance = 0.0;return balance;}public Integer bankAccount() {return bankAccount;}public void payForServices(Double amount) {currentBalance += amount;} }現在讓我們在我們的“付款應用程序”中添加幾個簽約公司并模擬向他們的一些付款:
public class PaymentApplication {public static void main(final String... args) {// InitializationPaymentSystem paymentSystem = new PaymentSystemV1();CommissionEmployee johnSmith = new CommissionEmployee("John Smith", 1111, 300.0, 100.0);paymentSystem.addPayee(johnSmith);CommissionEmployee paulJones = new CommissionEmployee("Paul Jones", 2222, 350.0, 125.0);paymentSystem.addPayee(paulJones);SalaryEmployee maryBrown = new SalaryEmployee("Mary Brown", 3333, 500.0, 110.0);paymentSystem.addPayee(maryBrown);SalaryEmployee susanWhite = new SalaryEmployee("Susan White", 4444, 470.0, 130.0);paymentSystem.addPayee(susanWhite);ContractingCompany acmeInc = new ContractingCompany("Acme Inc", 5555);paymentSystem.addPayee(acmeInc);ContractingCompany javaCodeGeeks = new ContractingCompany("javacodegeeks.com", 6666);paymentSystem.addPayee(javaCodeGeeks);// Simulate WeekjohnSmith.giveCommission(40.0);johnSmith.giveCommission(35.0);johnSmith.giveCommission(45.0);johnSmith.giveBonus(5.0);paulJones.giveCommission(45.0);paulJones.giveCommission(51.0);paulJones.giveCommission(23.0);paulJones.giveCommission(14.5);paulJones.giveCommission(57.3);paulJones.giveBonus(6.5);maryBrown.giveBonus(3.0);susanWhite.giveBonus(7.5);acmeInc.payForServices(100.0);acmeInc.payForServices(250.0);acmeInc.payForServices(300.0);javaCodeGeeks.payForServices(400.0);javaCodeGeeks.payForServices(250.0);// Process Weekly PaymentpaymentSystem.processPayments();}輸出:
Paying to John SmithGross 442.5Transferred to Account: 1111 Paying to Paul JonesGross 574.925Transferred to Account: 2222 Paying to Mary BrownGross 515.0Transferred to Account: 3333 Paying to Susan WhiteGross 505.25Transferred to Account: 4444 Paying to Acme IncGross 650.0Transferred to Account: 5555 Paying to javacodegeeks.comGross 650.0Transferred to Account: 6666現在,我們已經成功地向系統中添加了一種全新的收款人類型,而無需更改處理收款人的PaymentSystem類中的一行代碼。 這就是抽象的力量。
先進的功能:稅收
老板正在使用支付系統上空,但是收稅員給他發了一封信,告訴他必須將某種預扣稅納入系統中,否則會遇到大麻煩。 該系統應該有一個全球稅率,并且每個雇員都應該有個人免稅額。 僅應在向員工支付的免稅津貼之外的任何款項中收稅。 然后,應僅向雇員支付應付給他們的凈付款額。 訂約公司負責繳納自己的稅款,因此系統不應向其代扣代繳稅款。
為了處理稅收,我們需要創建一種新的特殊收款人類型,該收款人應納稅并且可以提供免稅額度。
在Java中,我們可以擴展接口。 這讓我們在不修改原始接口的情況下添加行為定義。 原始接口中定義的所有行為將自動帶入新接口。
TaxablePayee界面
我們將擴展Payee以創建新的TaxablePayee接口,然后讓Employee實現該接口,同時讓ContractingCompany保持為常規的免稅Payee。
類圖
圖5
碼
public interface TaxablePayee extends Payee {public Double allowance();}在這里,我們看到Payee進行了擴展,并定義了另一個方法– allowance() ,該方法返回TaxablePayee的免稅津貼。
現在,我們需要更新Employee以實現TaxablePayee,我們將看到這將對兩個具體的Employee類產生影響。
public abstract class Employee implements TaxablePayee {private String name;private Integer bankAccount;private Double allowance;protected Double currentBonus;protected Double grossWage;public Employee(String name, Integer bankAccount, Double grossWage, Double allowance) {this.name = name;this.bankAccount = bankAccount;this.grossWage = grossWage;this.allowance = allowance;currentBonus = 0.0;}public String name() {return name;}public Integer bankAccount() {return bankAccount;}public Double allowance() {return allowance;}public abstract void giveBonus(Double percentage);protected Double doCurrentBonus() {Double bonus = currentBonus;currentBonus = 0.0;return bonus;}}我們不得不更改Employee的構造函數,這意味著我們兩個抽象類的構造函數也將需要更改。
public class SalaryEmployee extends Employee {public SalaryEmployee(String name, Integer bankAccount, Double grossWage, Double allowance) {super(name, bankAccount, grossWage, allowance);}@Overridepublic void giveBonus(Double percentage) {currentBonus += grossWage * (percentage/100.0);}@Overridepublic Double grossPayment() {return grossWage + doCurrentBonus();} }傭金員工:
public class CommissionEmployee extends Employee {private static final Double bonusMultiplier = 1.5;private Double grossCommission = 0.0;public CommissionEmployee(String name, Integer bankAccount, Double grossWage, Double allowance) {super(name, bankAccount, grossWage, allowance);}@Overridepublic void giveBonus(Double percentage) {currentBonus += grossWage * (percentage/100.0) * bonusMultiplier;}@Overridepublic Double grossPayment() {return grossWage + doCurrentBonus() + doCurrentCommission();}private Double doCurrentCommission() {Double commission = grossCommission;grossCommission = 0.0;return commission;}public void giveCommission(Double amount) {grossCommission += amount;} }PaymentSystem中的稅收變更
現在,我們需要更新PaymentSystem以預扣稅,并且為此,我們需要某種方式來確定給定的收款人是TaxablePayee還是常規的收款人。 我們可以利用一個名為instanceof的Java關鍵字來做到這一點。
Instanceof讓我們檢查給定引用變量的運行時類型是否與測試類型匹配。 它返回一個布爾值,可以這樣調用:if(MyClass的object1 instance)。 如果object1的運行時類型是MyClass 或 MyClass 的子類或實現類(如果MyClass是接口) ,則返回true。 這意味著我們可以在繼承樹上的任何地方進行測試,使其成為非常強大的工具。 我們可以使用此工具來確定給定的收款人是否是TaxablePayee的實例,并根據該知識采取適當的措施。
現在,我們按以下方式更新PaymentSystem:
public class PaymentSystem {private List<Payee> payees;private Double taxRate = 0.2;public PaymentSystem() {payees = new ArrayList<>();}public void addPayee(Payee payee) {if (!payees.contains(payee)) {payees.add(payee);}}public void processPayments() {for (Payee payee : payees) {Double grossPayment = payee.grossPayment();Double tax = 0.0;if (payee instanceof TaxablePayee) {Double taxableIncome = grossPayment - ((TaxablePayee)payee).allowance();tax = taxableIncome * taxRate;}Double netPayment = grossPayment - tax;System.out.println("Paying to " + payee.name());System.out.println("\tGross\t" + grossPayment);System.out.println("\tTax\t\t-" + tax);System.out.println("\tNet\t\t" + netPayment);System.out.println("\tTransferred to Account: " + payee.bankAccount());}} }如果收款人被處理的新代碼首先檢查是TaxablePayee的一個實例,如果是則它在收款人的鑄造把它作為參考,以一個TaxablePayee用于訪問的目的allowance()在TaxablePayee定義的方法。 記得; 如果引用保留為收款人,則allowance()方法在PayableSystem中不可見,因為它是在TaxablePayee中定義的,而不是在Payee中定義的。 由于我們已經確認收款人是TaxablePayee的一個實例,因此此處的轉換是安全的。 現在,PaymentSystem有了免稅額,它可以根據20%的全球稅率計算應納稅額和要預扣的稅款。
如果收款人不是TaxablePayee,則系統會繼續正常處理它們,而不進行與稅務處理有關的任何操作。
讓我們更新應用程序類,為我們的員工提供不同的免稅額度,然后再次執行該應用程序:
public class PaymentApplication {public static void main(final String... args) {// InitializationPaymentSystem paymentSystem = new PaymentSystemV1();CommissionEmployee johnSmith = new CommissionEmployee("John Smith", 1111, 300.0, 100.0);paymentSystem.addPayee(johnSmith);CommissionEmployee paulJones = new CommissionEmployee("Paul Jones", 2222, 350.0, 125.0);paymentSystem.addPayee(paulJones);SalaryEmployee maryBrown = new SalaryEmployee("Mary Brown", 3333, 500.0, 110.0);paymentSystem.addPayee(maryBrown);SalaryEmployee susanWhite = new SalaryEmployee("Susan White", 4444, 470.0, 130.0);paymentSystem.addPayee(susanWhite);ContractingCompany acmeInc = new ContractingCompany("Acme Inc", 5555);paymentSystem.addPayee(acmeInc);ContractingCompany javaCodeGeeks = new ContractingCompany("javacodegeeks.com", 6666);paymentSystem.addPayee(javaCodeGeeks);// Simulate WeekjohnSmith.giveCommission(40.0);johnSmith.giveCommission(35.0);johnSmith.giveCommission(45.0);johnSmith.giveBonus(5.0);paulJones.giveCommission(45.0);paulJones.giveCommission(51.0);paulJones.giveCommission(23.0);paulJones.giveCommission(14.5);paulJones.giveCommission(57.3);paulJones.giveBonus(6.5);maryBrown.giveBonus(3.0);susanWhite.giveBonus(7.5);acmeInc.payForServices(100.0);acmeInc.payForServices(250.0);acmeInc.payForServices(300.0);javaCodeGeeks.payForServices(400.0);javaCodeGeeks.payForServices(250.0);// Process Weekly PaymentpaymentSystem.processPayments();} }輸出:
Paying to John SmithGross 442.5Tax -68.5Net 374.0Transferred to Account: 1111 Paying to Paul JonesGross 574.925Tax -89.985Net 484.93999999999994Transferred to Account: 2222 Paying to Mary BrownGross 515.0Tax -81.0Net 434.0Transferred to Account: 3333 Paying to Susan WhiteGross 505.25Tax -75.05Net 430.2Transferred to Account: 4444 Paying to Acme IncGross 650.0Tax -0.0Net 650.0Transferred to Account: 5555 Paying to javacodegeeks.comGross 650.0Tax -0.0Net 650.0Transferred to Account: 6666如您所見,稅收是為員工計算和預扣的,而簽約公司繼續不交稅。 老板不能對這個系統感到滿意,而是要給我們一個帶薪休假的全部費用,以使他免受監禁!
5.結論
如果以正確的方式使用抽象,它是Java中非常強大的工具。 它為高級Java使用以及構建復雜,可擴展和可維護的軟件打開了許多可能性。 我們只是簡單地了解了抽象可以為我們做些什么的表面,并希望為了解可以更詳細地使用抽象的不同方式奠定基礎。
翻譯自: https://www.javacodegeeks.com/2014/07/abstraction-in-java.html
總結
以上是生活随笔為你收集整理的Java抽象– ULTIMATE教程(PDF下载)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 硫磺皂洗澡的好处与坏处(硫磺皂洗脸洗澡要
- 下一篇: Maven Git发布