编者注 :在本文中,我们提供了Java教程中的全面抽象。 抽象发生在类级别的设计中,目的是隐藏实现API /设计/系统提供的功能的方式的实现复杂性,从某种意义上讲简化了访问底层实现的“接口”。 此过程可以在越来越“更高”的抽象层次(层)上重复进行,从而可以在较大的层上构建大型系统而不会增加代码和理解的复杂性。
目录
- 1.简介 2.接口
- 2.1。 定义接口 2.2。 实施接口 2.3。 使用介面
3.抽象类 - 3.1。 定义抽象类 3.2。 扩展抽象类 3.3。 使用抽象类
4.一个可行的例子–付款系统 - 4.1。 收款人界面 4.2。 付款系统 4.3。 员工班 4.4。 应用程序 4.5。 处理红利 4.6。 承包公司 4.7。 先进的功能:税收
5.结论
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类
让我们从“受薪雇员”课程开始。 它应该实现“收款人”界面,以便支付系统可以使用它。
让我们使用代码之外的类图来表示更改。 在类图中,虚线箭头表示“实现”关系,实线箭头表示“扩展”关系。
类图
码
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非常相似。
类图
码
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()
提供不同的实现。
类图
码
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,以便付款系统可以使用它。 它应该有一种支付服务的方法,该方法将通过累计总数进行跟踪并用于支付。
类图
码
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。
类图
码
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