本部分索引
6.9 抽象类与接口的应用
6.9.1 为抽象类与接口实例化
6.9.2 抽象类的实际应用 -- 模板设计
6.9.3 接口的实际应用 -- 制定标准
6.9.4 设计模式 -- 工厂设计
6.9.5 设计模式 -- 代理设计
6.9.6 设计模式 -- 适配器设计
6.9.8 抽象类与接口之间的关系
6.9 抽象类与接口的应用
6.9.1 为抽象类与接口实例化
在java中可以通过对象的多态性来实例化抽象类和接口,这样再使用抽象类和接口时即可调用本子类中所覆写过的方法。
1. 实例化抽象类
abstract class A{
public abstract void print();
}
class B extends A{
public void print(){
System.out.println("Hello World!");
}
}
public class AbstractCaseDemo01{
public static void main(String args[]){
A a = new B(); //<span style="font-size:14px;color:#ff0000;background-color: rgb(255, 255, 0);">通过子类来实例化抽象类,有向上转型的关系发生</span>
a.print();
}
}
2. 实例化接口
interface A{
public abstract void print();
}
class B implements A{
public void print(){
System.out.println("Hello World!!!");
}
}
public class InterfaceCaseDemo01{
public static void main(String args[]){
A a = new B(); //<span style="color:#ff0000;background-color: rgb(255, 255, 0);">通过子类来实例化接口,注意这里又向上转型了。。。。</span>
a.print(); //<span style="color:#ff0000;background-color: rgb(255, 255, 0);">发生向上转型后,调用的方法始终是被子类所覆写过的方法!!!</span>
}
}
通过以上的两组代码中可以发现,程序通过对象的多态性为抽象类及接口进行实例化,这样所调用的方法就是被子类所覆写过的方法。
6.9.2 抽象类的实际应用 -- 模板设计
有以下这种场景: 假设人分为学生和工人,学生和工人都可以说话,但是他们说话的内容不一样,也就是说,说话这个功能应该是一个具体的功能,而说话的内容就要由学生或者工人来决定了。 此时就可以使用抽象类实现这种场景。
1. 抽象类的实际应用
abstract class Person{
private String name;
private int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
public void say(){ //<span style="color:#ff0000;">说话是具体功能,要定义成普通方法</span>
System.out.println(this.getContent()); //输出说话的内容
}
public abstract String getContent(); //<span style="color:#ff0000;">说话的内容则由子类决定。</span>
}
class Student extends Person{
private float score;
public Student(String name, int age, float score){
super(name, age); //<span style="color:#ff0000;">调用父类的构造方法</span>
this.score = score;
}
public String getContent(){ //<span style="color:#ff0000;">覆写父类中的抽象方法,父类中所有抽象方法在子类中必须得到实现!!!</span>
return "Student info --> name: " + super.getName() + "age: " + super.getAge() + "Score " + this.score;
}
}
class Worker extends Person{
private float salary;
public Worker(String name, int age, float salary){
super(name, age);
this.salary = salary;
}
public String getContent(){
return "Worker Info --> Name: " + super.getName() + "age: " + super.getAge() + "salary: " + this.salary;
}
}
public class AbstractCaseDemo02{
public static void main(String args[]){
Person per1 = null; //声明Person对象
Person per2 = null;
per1 = new Student("forfan06", 18, 67.0f); //Student和Worker都是属于Person类
per2 = new Worker("forfan07", 28, 2576.0f);
per1.say();
per2.say();
}
}
从程序的运行结果可以发现,在Person类中就相当于定义了一个模板,在主方法调用时,调用的就是普通方法,而子类只需要实现父类中的抽象方法,就可以取得一个具体的信息。
6.9.3 接口的实际应用 -- 制定标准
接口,是java解决多继承局限的一种手段。 而且接口可以通过对象的多态性来实例化。 但是接口在实际中更多的作用是用来制定标准。
例如,U盘和打印机都可以插在电脑上使用,这是因为它们都实现了USB的接口,对于计算机来说,只要是符合了USB接口标准的设备就都可以插进来。
1. 制定USB标准
interface USB{ //定义USB接口
public void start(); //USB设备开始工作
public void stop(); //USB设备停止工作
}
class Computer{
public static void plugin(USB usb){ //电脑上可以插入USB设备
usb.start();
System.out.println("*********USB device works*******");
usb.stop();
}
}
class Flash implements USB{
public void start(){ //覆写父类中的所有抽象方法!!!
System.out.println("Flash was starting USB开始工作");
}
public void stop(){
System.out.println("Flash was stopping USB停止工作");
}
}
class Print implements USB{
public void start(){ //覆写父类中的所有抽象方法!!!
System.out.println("Print was starting!! 打印机开始工作");
}
public void stop(){
System.out.println("Print was stopping!!!打印机停止工作");
}
}
public class InterfaceCaseDemo02{
public static void main(String args[]){
Computer.plugin(new Flash());
Computer.plugin(new Print());
}
}
接口实际上还表示将方法的视图暴露给远程的客户端。
6.9.4 设计模式 -- 工厂设计
工厂设计模式,是在java开发中最常使用的一直设计模式。 什么是工厂设计模式,我们通过下面的代码来观察。
interface Fruit{
public void eat();
}
class Apple implements Fruit{
public void eat(){
System.out.println("***class Apple***");
}
}
class Orange implements Fruit{
public void eat(){
System.out.println("***class Orange***");
}
}
public class InterfaceCaseDemo03{
public static void main(String args[]){
//想实例化一个Fruit对象,按照上面讲解的利用对象多态性来实例化Fruit对象
Fruit f = new Apple();
f.eat();
}
}
分析: 主方法,应该就只表示一个客户端。主方法中的代码应该越少越好。 此时直接在主方法中指定了要操作的子类, 如果要更换子类,则肯定要修改客户端。上面的例子表示跟特定的子类紧密地耦合在一起。这个时候肯定要解偶合。
JVM的工作原理: 程序 --> JVM --> 操作系统 (此时JVM就是一个客户端)
采用设计工厂来重新编写上面的例子
interface Fruit{
public void eat();
}
class Apple implements Fruit{
public void eat(){
System.out.println("***class Apple***");
}
}
class Orange implements Fruit{
public void eat(){
System.out.println("***class Orange***");
}
}
class Factory{
public static Fruit getInstance(String classname){
Fruit f = null;
if(classname.equals("apple")){
f = new Apple();
}
if(classname.equals("orange")){
f = new Orange();
}
return f;
}
}
public class InterfaceCaseDemo04{
public static void main(String args[]){
//想实例化一个Fruit对象,按照上面讲解的利用对象多态性来实例化Fruit对象
//Fruit f = new Apple();
//使用工厂设计模式后,只需要客户端输入代表特定子类的特定字符串,就可以在工厂里面实例化所需要的实例化对象
Fruit f = Factory.getInstance("apple"); //通过指定字符串apple,告诉Factory客户需要的Apple的子类
f.eat();
}
}
思考1 : 假如此时客户端没有指定任何字符串,即 主方法中 Fruit f = Factory.getInstance(null); 那么会得到空指向的error message
-------------------------------------------------
Exception in thread "main" java.lang.NullPointerException at Factory.getInstance(Unknown Source) at InterfaceCase03.main(Unknown Source)
执行错误
-------------------------------------------------
null空值不能调用方法,所以在进行字符串判断时,要把字符串常量写在前面,否则会出现空指向异常。NullPointerException.
此时修改Factory里面的字符串判断如下:
class Factory{
public static Fruit getInstance(String classname){
Fruit f = null;
if("apple".equals(classname)){
f = new Apple();
}
if("orange".equals(classname)){
f = new Orange();
}
return f;
}
}
思考2: 如果客户端通过工厂类Factory 得到的还是一个null空对象的话,也会抛出空指向异常,所以也需要判断是否得到了一个非空的实例化对象。 修改后合理的整体代码如下
interface Fruit{
public void eat();
}
class Apple implements Fruit{
public void eat(){
System.out.println("***class Apple***");
}
}
class Orange implements Fruit{
public void eat(){
System.out.println("***class Orange***");
}
}
class Factory{
public static Fruit getInstance(String classname){
Fruit f = null;
if("apple".equals(classname)){
f = new Apple();
}
if("orange".equals(classname)){
f = new Orange();
}
return f;
}
}
public class InterfaceCaseDemo07{
public static void main(String args[]){
//想实例化一个Fruit对象,按照上面讲解的利用对象多态性来实例化Fruit对象
//Fruit f = new Apple();
//使用工厂设计模式后,只需要客户端输入代表特定子类的特定字符串,就可以在工厂里面实例化所需要的实例化对象
Fruit f = Factory.getInstance(args[0]); //通过指定字符串apple,告诉Factory客户需要的Apple的子类
if (f != null){ //判断是否取得实例化对象
f.eat();
}
}
}
6.9.5 设计模式 -- 代理设计
代理设计也是在java开发中使用较多的一种设计模式,所谓的代理设计就是指由一个代理主题来操作真实主题,真实主题制定具体的业务操作,而代理主题负责其他相关业务的处理,处理完其他业务后,再交给真实主题来执行客户所关心的业务操作。
代理设计就好像生活中经常使用的代理上网一样,客户通过网络代理连接网络,由代理服务器完成用户权限和访问限制等与上网操作相关的操作。
不管是代理操作还是真实的业务操作,其共同点目的就是上网, 所以用户关心的只是如何上网,至于具体是如何操作的用户并不关心。如何用java来实现呢? 只需要订一个上网的接口,代理主题和真实主题都同时实现此接口,然后再由代理操作真实主题即可,代码如下:
interface NetWork{ //定义Network接口
public void browse(); //定义浏览的抽象方法
}
class Real implements NetWork{
public void browse(){ //实现接口中的抽象方法
System.out.println("上网浏览成功");
}
}
class Proxy implements NetWork{
private NetWork network; //代理对象
public Proxy(NetWork network){ //设置代理的真实操作
this.network = network; //设置代理的子类
}
public void Check(){ //与具体上网相关的操作,例如是否有权限访问等等
System.out.println("检查用户是否合法");
}
public void browse(){ //实现接口中的抽象方法
this.Check(); //可以同时调用多个与具体业务相关的操作
this.network.browse(); //调用真实上网操作
}
}
public class ProxyDemo01{
public static void main(String args[]){
NetWork net = null; //定义接口对象
net = new Proxy(new Real()); //实例化代理,同时传入代理的真实操作
net.browse(); //客户端实际上只关心上网浏览一个功能
}
}
真实主题完成的只是上网的最基本功能,而代理主题要做比真实主题更多的业务相关的操作。
6.9.6 设计模式 -- 适配器设计
如果一个类要实现一个接口,则必须要覆写此接口中的全部抽象方法。假如此时接口中定义的抽象方法过多,子类中又用不到这么多的抽象方法,如果直接用该子类去实现该接口的话,肯定会造成代码冗余等等麻烦。我们此时可以增加一个中间的过渡类,同时我们不希望此过渡类被直接使用,所以将此过渡类定义成抽象类最为合适。即一个接口首先被一个抽象类先实现(此抽象类通常称为适配器类),并在此抽象类中实现若干方法(此时方法体为空!!实际上在适配器类中没有详细具体的实现该抽象法),则以后继承自该适配器类的子类就可以有选择性的覆写所需要的方法。
1. 要实现接口中的全部抽象方法,但是又不希望该类被直接使用。 所以定义成抽象类
2. 该抽象类中,没有抽象方法。所以该抽象类的子类可以不必覆写适配器类中所有的方法,换言之,就是子类不需要覆写接口的全部抽象方法。
实现代码如下:
interface _Window{ //定义_Window接口,包括很多抽象方法
public void open();
public void close();
public void activated();
public void iconified();
public void deiconified();
}
abstract class WindowAdapter implements _Window{ //定义一个抽象类,<span style="color:#ff0000;background-color: rgb(255, 255, 0);">实现接口的全部抽象方法,但是方法体为空!!!!</span>
public void open() {} //覆写接口的全部抽象方法,但是方法体全部为空!! <span style="font-size:14px;color:#ff0000;background-color: rgb(255, 255, 0);">“{}”表示该方法已经实现~~</span>
public void close() {}
public void activated() {}
public void iconified() {}
public void deiconified() {}
}
class WindowImpl extends WindowAdapter{ //继承适配器类,有选择的覆写适配器类中的方法,来实现需要的方法。 因为此时<span style="font-size:18px;color:#ff0000;background-color: rgb(255, 255, 0);">适配器类是抽象的,但是里面的方法都不是抽象的,所以没有规定必须全部覆写</span>
public void open(){ //真正的实现open()方法
System.out.println("open window operation");
}
public void close(){
System.out.println("close window operation");
}
}
public class WindowAdapterDemo01{
public static void main(String args[]){
_Window win = new WindowImpl(); //实现接口对象
win.open(); //调用open方法,<span style="font-size:14px;color:#ff0000;background-color: rgb(255, 255, 0);">对象多态性 -->调用的始终是被子类所覆写过的方法</span>
win.close();
}
}
*******适配器设计模式,在java图形界面编程的事件处理中经常使用到*******
6.9.7 内部类的扩展
在抽象类中可以定义多个内部抽象类或者接口,在接口中可以定义多个内部抽象类或接口。 了解即可
6.9.8 抽象类与接口之间的关系
No. | 区别点 | 抽象类 | 接口 |
1 | 定义 | 包含抽象方法的类 | 抽象方法和全局常量的集合 |
2 | 组成 | 构造方法、抽象方法、普通方法、常量、变量 | 全局常量、抽象方法 |
3 | 使用 | 子类继承抽象类(extends) | 子类实现接口 implements |
4 | 关系 | 抽象类可以实现多个接口 | 接口不能继承抽象类,但允许继承多个接口 |
5 | 常见设计模式 | 模板设计 | 工厂设计、代理设计、适配器设计 |
6 | 对象 | 都通过对象的多态性产生实例化对象(对象向上转型) | |
7 | 局限 | 抽象类有单继承的局限 | 接口没有此局限 |
8 | 实际应用 | 作为一个模板 | 是作为一个标准或表示一种能力 |
9 | 选择 | 如果抽象类和接口都可以使用的话,优先使用接口,因为可以避免单继承的局限 | |
10 | 特殊 | 一个抽象类中可以包含多个接口,一个接口中可以包含多个抽象类 |
Tips: 一个类永远不要去继承一个已经实现好的类,要么继承抽象类,要么实现接口。如果接口和抽象类都可以使用的话,优先使用接口,避免但继承的局限性!!