浅谈JAVA中抽象类的使用

152 阅读6分钟
说起抽象类,想必写JAVA的同学都非常熟悉,关于其概念,我也就不赘述了。可是在实际开发中,我们又是否有使用过抽象类这个东西呢?之前也问过几个同学,得到的的答案几乎都是在项目中从来没过抽象类。其实可以理解,因为日常开发工作中,大部分都是在既定的技术框架中根据需求编写业务代码。笔者自己之前也是如此,开发需求的时候直接就是按照下图中那样分好包和类,一套典型的标准连招,抽象类是啥,从没写过……直到一年多以前,在前领导的推荐下去看了部门大佬demonquan写的一个项目代码,才让我对这个“熟悉又陌生”的抽象类有了新的认知……



扯了一些有的没的,那么抽象类这个东西究竟在什么场景下用到呢?下面进入正题

以笔者自身为例,由于家里没矿,所以只能老老实实当个打工仔嘛,所以站在笔者老板的角度来看,笔者的身份就是个小员工嘛,老板雇你来干嘛呢,干活呗,于是就可以用以下接口来描述笔者以及笔者的同事们

public interface Employee {
    void work();
}

看到这里可能会说,员工怎么定义成接口,而且只有一个工作方法,连姓名啥的都不配有?真就工具人呗? 莫急莫急,当然配拥有姓名,我们打工仔也是有尊严的好吗!我们将姓名、年龄、性别这些基本属性定义在以下的Person类中

public class Person {
    protected String name;
    
    protected Integer age;
    
    protected String gender;

    public Person(String name, Integer age, String gender) {
        this.name = name;       
        this.age = age;       
        this.gender = gender;    
    }
}

那为什么要这样定义呢,我们都知道JAVA中接口其实像是一个行为的约束与规范嘛,实现接口的类就必须要实现它当中的方法,那么对于你的老板而言,对你最大的关注点自然在你干不干活嘛,哪管你的姓名、年龄、性别呢?(哈哈,开个玩笑,开个玩笑~)

接着说,一个公司都有很多岗位嘛,什么人资呀会计呀等等,每个员工都有自己的岗位,那笔者自己是个程序员嘛,以程序员这个岗位为例,按照能力来划分又可以初级程序员、高级程序员等等。有的看官看到这可能又要急了,你扯这些乱七八糟的岗位和职级,和抽象类有什么关系呀?你这不是典型的“标题党”吗?!莫急,且往下看……

刚才说了程序员是员工中的一种,如果要用一个类来描述程序员的话,那自然实现员工接口,同时程序员也具备姓名和年龄等属性,因此还需要继承Person类。刚才也说了,员工嘛,干活才是重点。那么程序员平时是怎么工作的呢?我姑且简单将它分为两步1.写代码2.提交代码,这么一来我们可以用以下的代码来描述程序员

public class Programmer extends Person implements Employee {
    public Programmer(String name, Integer age, String gender) {
        super(name, age, gender);    
    }
        
    @Override    
    public void work() {
        coding();        
        submitCode();    
    }    
    
    private void coding(){
        System.out.println("写代码");    
    }    
    
    private void submitCode(){
        System.out.println("提交代码到git仓库");    
    }
}

看到这里,好像都结束了,用上面的类来定义一个程序员,活也干了,名字年龄性别也有了,完美! 怎么可能呢?我们本篇的主角抽象类都还没出场呢!

刚才也说了,程序员是分职级的,有专写bug的菜鸟程序员,也有能写架构的高级程序员,那如果我们要分别用类来描述高级程序员和菜鸟程序员,该如何定义呢?其实简单,上文提到,程序员干活,简单来说就两个步骤,写代码和提交代码。而菜鸟程序员和高级程序员的主要的区别其实就在写代码这一环,因为提交代码就是一个git命令的事,对所有程序员都一样。那么说到这里了,到底该怎样才好呢?首先,将上文提到的Programmer类做一些修改,如下

public abstract class Programmer extends Person implements Employee{
    public Programmer(String name, Integer age, String gender) {
        super(name, age, gender);    
    }    
    
    @Override    
    public void work() {
        coding();        
        submitCode();    
    }
        
    protected abstract void coding();    
    
    protected void submitCode(){
        System.out.println("提交代码到git仓库");    
    }
}

可以看到,我将Programmer类改成了抽象类,并将coding(写代码)方法改成了抽象的,原因刚才说了,不同职级的程序员写代码的具体实现是不同的,因此抽象出来交给具体子类(各职级的程序员)去实现。此外还修改了方法的修饰符,方便继承。这么一来,我们的菜鸟程序员和高级程序员就很容易用类来描述了,代码如下

高级程序员:

public class SeniorProgrammer extends Programmer {
    public SeniorProgrammer(String name, Integer age, String gender) {
        super(name, age, gender);    
    }    
    
    @Override    
    protected void coding() {
        System.out.println("写架构");    
    }
}

菜鸟程序员:

public class NoobProgrammer extends Programmer {
    public NoobProgrammer(String name, Integer age, String gender) {
        super(name, age, gender);    
    }    
    
    @Override    
    protected void coding() {
        System.out.println("写bug");    
    }
}

接下来我们写个Main方法来测试一下

public class Main {    
    public static void main(String[] args) {
        Employee shenao=new SeniorProgrammer("申奥",23,"mail"); 
        Employee pengfei=new NoobProgrammer("鹏飞",24,"femail");
        shenao.work();        
        System.out.println("------------分隔符-------------");
        pengfei.work();    
    }
}

运行结果如下:


这么一来,一切就都搞定啦!

看到这里,可能还是有的看官没有明白,抽象类到底在什么场景下使用呢?所谓抽象类,顾名思义,核心就在“抽象“二字,那么又该如何抽象呢?在笔者看来可以分为以下几个步骤:

首先,我们在根据业务来设计接口的时候,要搞清楚接口应该具备哪些行为(方法),再思考一下接口的这些行为是否可以细分成多个小行为,如果可以,那么就在接口与接口的实现类的中间添加一层抽象类,将这些相同的小行为具体的写在抽象类中,将那些不同的小行为定义成抽象,交给具体实现类去实现。当然,各个实现类中如果有相同的属性,同样也可以定义在抽象类中去。用我自己总结出来的一句话来描述就是:抽象类用于“具象”各个具体实现类中的最大公共部份,抽象各个实现类的差异部份。不知道我这样描述,看官们是否能够理解……

其实看完我上面的代码,有的看官可能会发现它与设计模式中的模板方法极其相似。是的,本文看似在写抽象类,其实是想用此来引发一个思考:在某些业务场景下,如何能够更好的对类进行设计,如何设计能够让自己的代码拓展性更强,可读性更高,而抽象类只不过是其中的一个手段而已。我认为,对于一个上进的程序员而言,“如何设计出更好的代码”将会是个职业生涯都在思考的问题……

文末,灵魂拷问:今天的你,比昨天更强了吗?如果没有,明天请努力!我是野原鑫之助,一个爱唱歌的菜鸟小码农,我们下期见……