java基础之接口和抽象类

接口在开发中的作用

一、抽象概念

抽象与具体相对,是对事物固定行为的概括化描述(只说明做什么,不说明怎么做)。

那个男人说:一千个读者眼中就会有一千个哈姆雷特。

在编程世界中亦是如此:只告诉你应该做什么(功能),但是没有告诉你怎么去做,怎么做需要你自己去思考(实现)。

强制性要求:抽象世界中,在接口、抽象类中定义了子类中必须进行实现的内容。

在继承和多态中完成继承,多态在开发中的作用:面向抽象编程,不需要再面向具体编程,降低代码耦合性,提供扩展性

二、抽象类

2.1、抽象类与抽象方法

  • 抽象类与具体类相对,是 Java 中使用关键词 abstract 修饰的类(表示不能实例化对象)
  • 抽象方法是抽象类中使用关键字 abstract 修饰的方法(只有声明没有方法体,需要由子类去进行实现)

2.2、抽象类的特点

  • 抽象类不能实例化对象
  • 如果从抽象类继承一个类,就必须为抽象类中的所有抽象方法提供方法的定义,否则继承的类也会变成抽象类
  • 即使不包括任何 abstract 方法,亦可将一个类声明成抽象类

2.3、抽象类与具体类的区别

具体类如果继承抽象类则需要重写所有抽象类的抽象方法

  • 抽象类

抽象类是对具体类的固定行为的概括化描述,即只有方法声明,没有方法体(具体实现),所以抽象类无法实例化对象

  • 具体类

具体类(或普通类)要求所有方法包含方法声明和方法体,所以具体类可以实例化对象

2.4、抽象类例子

抽象类中的抽象方法可以限制子类的功能(必须实现抽象方法),一般方法可以达到子类的代码复用(也可以重写方法)

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
    private static final long serialVersionUID = 1L;
    private transient ServletConfig config;

    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }

    public void init() throws ServletException {
    }


    public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

}

比如说这里的init(ServletConfig)进行重写,然后GenericServlet单独抽取出来一个init方法给子类进行调用。对于service(ServletRequest , ServletResponse)方法来说,因为子类不知道service方法要干嘛,所以无法进行实现。但是每个继承GenericServlet的类都需要重写service(ServletRequest , ServletResponse)方法

实现类:HttpServlet

public abstract class HttpServlet extends GenericServlet {
  	  // 重写父类方法
  	  @Override
      public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        HttpServletRequest request;
        HttpServletResponse response;
        try {
            request = (HttpServletRequest)req;
            response = (HttpServletResponse)res;
        } catch (ClassCastException var6) {
            throw new ServletException(lStrings.getString("http.non_http"));
        }
	    // 然后子类自己实现
        this.service(request, response);
    }
 	// ......
}

2.5、小结

我感觉抽象类是一个有缺陷的类,因为无法创建对象,那么就无法通过'对象.方法'来进行调用。那么抽象类的意义何在?

就是为了让子类通过继承来进行调用,那么对于子类来说,那么存在一种情况:如果子类都有自己单独的实现,那么就各自实现自己的逻辑。但是如果在子类实现中存在着公用逻辑,那么又可以单独抽取到父类中来,然后子类再来进行调用。

那么模仿上面的类来写demo:

public class UserInfoDTO implements Serializable {
    private String username;
    private String password;
    private String prefix;
}
public abstract class AbstractJobService {

    /**
     * 连接远程和本地,但是方法实现不同
     */
    public abstract void initConnection(UserInfoDTO userInfoDTO);

    /**
     * 不同的拉取接口将会实现不同的服务
     * @param pullJobService
     */
    public abstract void service(PullJobService pullJobService);

}

新建接口,给子类使用:

public interface PullJobService {
}

那么各自的实现交给子类来做。

三、接口

接口在java中就是规范的意思,由接口来指定规范,要求子类中的方法强制执行里面的方法。最典型的就是Servlet和JDBC接口

下面来通过案例来进行演示:

3.1、接口

创建接口:

public interface Animal {
    void eat();
    void sleep();
}

3.2、实现类BaGe类

public class BaGe implements Animal {
    @Override
    public void eat() {
        System.out.println("八哥可以吃");
    }

    @Override
    public void sleep() {
        System.out.println("八哥可以睡觉");
    }
}

3.3、实现类YingWu类

public class YingWu implements Animal {
    @Override
    public void eat() {
        System.out.println("鹦鹉可以吃饭");
    }

    @Override
    public void sleep() {
        System.out.println("鹦鹉可以睡觉");
    }
}

3.4、创建Fly类:

public class Fly {
    // 八哥
    private BaGe ge;

    public Fly() {
    }

    public Fly(BaGe ge) {
        this.ge = ge;
    }

    public void doSome(){
        ge.eat();
        ge.sleep();
    }

}

2.6、测试类:

public class Test {
    public static void main(String[] args) {
        BaGe baGe = new BaGe();
        Fly fly = new Fly(baGe);
        fly.doSome();
    }
}

那么这样子来进行输出肯定是没有问题的。

但是如果此时又想在Fly类中让另外一种动物飞行,比如说让鹦鹉来进行操作,那么不得不去修改Fly类的源代码。

如下所示:

public class Fly {
    // 修改源代码为yingwu
    private YingWu yingWu;

    public Fly() {
    }

    public Fly(YingWu yingWu) {
        this.yingWu = yingWu;
    }

    public void doSome(){
        yingWu.eat();
        yingWu.sleep();
    }
}

然后来进行测试:

public class Test {
    public static void main(String[] args) {
        YingWu yingWu = new YingWu();
        Fly fly = new Fly(yingWu);
        fly.doSome();
    }
}

但是如果此时再去修改另外一种动物来进行飞行的话,那么还需要再次修改源代码。

那么这个时候抽象的概念就出来了。

3.7、体现抽象的设计

那么如果还是有对应的子类来进行实现的话,那么难道还是需要来修改源代码吗?所以这里抽象的功能就能够体现出来了。

我的理解:既然对于同样的功能来说,每个类的实现不同,那么就直接定义成抽象,实现了调用者和子类之间的解耦能力。这样子来进行操作更能够体现出来代码的扩展性和灵活性

直接修改Fly类,对应的代码如下所示:

public class Fly {
    private Animal animal;

    public Fly() {
    }

    public Fly(Animal animal) {
        this.animal = animal;
    }

    public void doSome(){
        animal.eat();
        animal.sleep();
    }
}

那么对应的测试类中:

public class Test {
    public static void main(String[] args) {
        Animal animal1 = new YingWu();
        Fly fly1 = new Fly(animal1);
        fly1.doSome();
        System.out.println("---------------------");
        Animal animal2 = new YingWu();
        Fly fly2 = new Fly(animal2);
        fly2.doSome();
    }
}

所以这种抽象的能力在程序中更能够将其体现出来,而且符合OCP开发原则(对扩展开放原则,对修改关闭)

接口是抽象的,所以之前所说的面向抽象编程在以后可理解成为面向接口编程。

总结:接口的作用,扩展性好,可插拔(更加具备这灵活度,如同USB,可以适配各种各样的类型,从而实现相同的功能)

但是相对于USB来说,是人为定义的一种规范,各大组织厂家都需要来进行遵守规范而已,针对于自己当前的业务场景来进行具体的实现,从而能够达到相同的效果而已。所以从这里可以看到接口是抽象的概念,而非是面向具体的实现。

四、类和类、类和接口设计原则

有三个硬性规定:

1、但凡是有 A has a B 的关系出现,那么就将都以属性的方式出现; 也就是A类中有一个属性B的出现;

2、但凡是有 A is a B的关系出现,那么就设置成继承关系出现,也就是A extends B的关系来进行实现;

3、但凡是有A like a B的关系出现,那么就设置成实现关系出现,月就是A implements B的形式出现;

五、接口+多态可以实现解耦合

接口在开发中肯定是离不开多态的:

interface Runnable{
    void run();
}

因为接口是无法来创建对象的,所以只能够来创建子类,而子类我们又可以自己来进行定义实现,所以将会导致不知道哪个是父类中的方法,哪个又是子类自己的方法,但是通常来说,我们都会来定义自己的方法。

接口+多态:达到的效果是解耦合

解耦解的是谁和谁的耦合?

解耦解的是调用者和接口实现类的耦合。

调用者支持面向接口编程;实现类面向接口编写实现

再次总结:面向接口开发,也就是面向抽象开发,解耦合,降低耦合度,符合OCP开发原则,提高程序扩展能力;

六、进一步思考

这里来做一个思考:用例子来进行演示:

顾客---->菜单-->中国厨师和西餐厨师

如果没有顾客,那么菜单和厨师都是可以存在的;

那么这样也就意味着在开发中,如果给定了接口,就可以让两个开发人员没有任何的关系进行独立开发的效果。

因为对于顾客来说,知道了菜单中的方法,但是不知道具体的实现;而对于厨师来说,只需要关注当前具体的实施,不需要管理另外的即可。

所以面向接口来进行开发是完成能够来做到解耦合的。思想层面上的东西。通过接口来接上去!!

不光如此,模块和模块之间也应该通过接口来进行操作;

七、抽象类和接口的使用

抽象类能不能完全抽象?如果完全抽象那么就和接口没有什么区别,那么一般的继承体系中都是以组合的方式来进行实现的。

也就是说:abstract implement interface,在抽象类中重写首先来继承,要么重新重写方法,要么就是提供一些方法来给子类。

可以看一下线程池中的Excutor的实现方法,以及List的实现方式,还有Servlet中的实现方式都是按照这种模式来进行操作的。

定义接口:

public interface FoodMenu {

    /**
     * 红烧肉
     * @return 返回的是布尔值
     */
    boolean hongshaorou();

    /**
     * 米饭
     */
    void mifan();
}

那么拿到了接口之后,对于调用者和实现者来说是解耦合了的。

对于调用者来说:

public class Customter {

    /**
     * 这里只需要来传递一个引用即可
     */
    private FoodMenu foodMenu;

    public Customter() {
    }

    /**
     * 通过有参构造来进行注入值
     * @param foodMenu 食物菜单
     */
    public Customter(FoodMenu foodMenu) {
        this.foodMenu = foodMenu;
    }

    /**
     * 顾客点单
     * @return
     */
    public Object order(){
        foodMenu.hongshaorou();
        foodMenu.mifan();
        return null;
    }
}

对于实现类来说:

ChinaFoodCooker实现类:

public class ChinaFoodCooker implements FoodMenu {
    @Override
    public boolean hongshaorou() {
        System.out.println("中国式做菜方式");
        return false;
    }

    @Override
    public void mifan() {
        System.out.println("东北大米饭");
    }
}

EnglishFoodCooker实现类:

public class EnglishFoodCooker implements FoodMenu {
    @Override
    public boolean hongshaorou() {
        System.out.println("英国式做菜方式");
        return false;
    }

    @Override
    public void mifan() {
        System.out.println("英国大米饭");
    }
}

那么来进行测试:

public class CustomerTest {
    public static void main(String[] args) {
        // FoodMenu foodMenu = new ChinaFoodCooker();
        FoodMenu foodMenu = new EnglishFoodCooker();
        // 这里可以根据具体的子类来进行选择,传递的值是哪个值就是哪个值,这个在自己的场景中也是可以来进行使用的
        Customter customter = new Customter(foodMenu);
        customter.order();
    }
}

我们在进行代码运行的时候,可以有选择性的去决定,用哪一个实现类来进行实现。那么对于Customer调用者来说,不需要来修改对应的源代码,只需要添加一个类,然后再调用的地方传入进去即可。

posted @ 2022-03-27 23:33  雩娄的木子  阅读(70)  评论(0编辑  收藏  举报