java学习笔记(六)-- (抽象类与接口上)(抽象类&模板设计模式)

抽象类的定义和使用

  • 抽象类的定义:抽象类只是比普通方法多了一些抽象方法而已。
  • 抽象方法:只声明而未实现的方法(没有方法体),抽象方法所在的类也一定要使用abstract来定义只声明而未实现的方法不一定为抽象方法,也可能为本地方法(调用c/c++库函数)

抽象类的使用原则

        1.所有抽象类必须有子类(final(没有子类)与abstract不能同时出现,矛盾)

        2.抽象类的子类必须覆写抽象类的所有抽象方法(private(不允许覆写)与abstract不能同时出现)

        3.抽象类无法直接创建实例化对象,需要通过子类向上转型为实例化 

举例:

abstract class Person{
    private String name;
    public String getName(){
        return name;
    }
    public abstract void test();
}
class Student extends Person{
    public void test(){
        System.out.println("子类覆写抽象方法!");
    }
}
public class Test{
    public static void main(String[] args) {
        Person per=new Student();
        per.test();
    }
}

 

抽象类相关规定

  • 抽象类一定存在构造方法,子类也一定遵循对象实例化流程。先调用父类构造再调用子类构造

举例:

abstract class Person{
    private String name;
    //构造方法
    public Person(){
        System.out.println("**********");
    }
    public String getName(){
        return this.name;
    }
    //普通方法
    public void setName(String name){
        this.name=name;
    }
    //抽象方法
    public abstract void getPersonInfo();
}
class Student extends Person{
    //构造方法
    public Student(){
        System.out.println("###########");
    }
    public void getPersonInfo(){
        //空实现
    }
}
public class Test{
    public static void main(String[] args) {
        new Student();
    }
}
//打印结果如下
// **********
// ###########

 

如果父类没有无参构造,那么子类构造必须使用super明确指出使用父类哪个构造方法

范例:  ****重要****

abstract class A{
    //4.调用父类构造
    public A(){
        //5.调用被子类覆写的方法
        this.print();
    }
    public abstract void print();
}
class B extends A{
    private int num=100;
    //2.调用子类实例化对象
    public B(int num){
        //3.隐含的super语句,实际要先调用父类构造
        super();
        //8.为类中属性初始化
        this.num=num;
    }
    //6.此时子类对象的属性还没有被初始化
    public void print(){
    //7.对应其数据类型的默认值
        System.out.println(this.num);
    }
}
public class Test{
    public static void main(String[] args) {
        //1.实例化子类对象
        new B(30);        
        new B(30).print();
    }
}
//打印结果如下:
//0

// 0
// 30
如果构造方法,那么对象中的属性一定都是其对应数据类型的默认值
对象实例化过程:
进行类加载
进行类对象的空间开辟
进行类对象的属性初始化(构造方法)
  • 抽象类可以没有任何抽象方法,但此时仍然不能创建实例化对象
  •  final与abstract不能同时出现;private与abstract也不能同时出现      具体原因:抽象类一定不能使用final声明,因为使用final声明的类不允许有子类;而抽象类必须有子类;相应的,抽象方 法也不能使用private定义,因为抽象方法必须要被覆写。

关于内部抽象类:子类只需要覆写外部抽象类的直接抽象方法。内部抽象类的抽象方法可不覆写。外部抽象类中不能使用static修饰,但是内部抽象类允许使用static

举例:

abstract class A{
    public abstract void printA();
    //内部类使用static修饰
    static abstract class B{
        public abstract void printB();
    }
}
class X extends A{
    public void printA(){}
}

 

 

**** 重点****

模板设计模式

设计模式:精髓解耦,引入第三方解耦(高内聚,低耦合)

开闭原则(OCP):一个软件实体如类。模块或函数应该对扩展开放,对修改关闭。

模板方法仅仅使用了Java的继承机制,但它是应用非常广泛的模式,抽象类的实际应用

模板方法定义了一个算法的步骤,并允许子类为一个或者多个步骤提供具体实现。

模板(模板方法)设计模式:基于抽象类

核心在一个方法定义一个算法的骨架,而将一些步骤延迟到子类中。模板模式可以使得子类在不改变算法的前提下,重新定义算法中的某些步骤。

注意:为了防止恶意操作,一般模板方法都加上final关键字, 不允许被覆写

模板方法的优点:
● 封装不变部分,扩展可变部分
● 提取公共部分代码,便于维护
● 行为由父类控制,子类实现

模板模式就是在模板方法中对基本方法的调用。

模板方法通用代码:

抽象模板类:

public abstract class AbstractClass {
     //基本方法
     protected abstract void doSomething();
     //基本方法
     protected abstract void doAnything();
     //模板方法
     public void templateMethod(){
             /*
              * 调用基本方法,完成相关的逻辑
              */
             this.doAnything();
             this.doSomething();
     }
}

具体模板类:注意:抽象模板中的基本方法尽量设计为protected类型,符合迪米特法则,不需要暴露的属性或方法尽量不要设置为protected类型

public class ConcreteClass1 extends AbstractClass {
     //实现基本方法
     protected void doAnything() {
             //业务逻辑处理
     }
     protected void doSomething() {
             //业务逻辑处理
     }
}
public class ConcreteClass2 extends AbstractClass {
     //实现基本方法
     protected void doAnything() {
             //业务逻辑处理
     }
     protected void doSomething() {
             //业务逻辑处理
     }
}

场景类:

public class Client {
     public static void main(String[] args) {
             AbstractClass class1 = new ConcreteClass1();
             AbstractClass class2 = new ConcreteClass2();               
             //调用模板方法
             class1.templateMethod();
             class2.templateMethod();
     }
}

模板模式举例·:

星巴克咖啡冲泡法

  1. 将水煮沸 
  2. 用沸水冲泡咖啡
  3. 将咖啡倒进杯子 
  4.  加糖和牛奶

星巴克茶冲泡法

  1. 将水煮沸 
  2. 用沸水浸泡茶叶 
  3. 把茶倒进杯子
  4. 加柠檬

根据冲泡步骤使用代码实现咖啡和茶的类:
 

class Coffee{
    //咖啡冲泡法(算法)
    void prepareRecipe(){
        boilWater();
        brewCoffeeGrings();
        pourInCup();
        addSugarAndMilk();
    }
    public void boilWater(){
        System.out.println("将水煮沸");
    }
    public void brewCoffeeGrings(){
        System.out.println("冲泡咖啡");
    }
    public void pourInCup(){
        System.out.println("将咖啡倒进杯子中");
    }
    public void addSugarAndMilk(){
        System.out.println("加糖和牛奶");
    }
}
class Tea{
    void prepareRecipe(){
        boilWater();
        steepTeaBag();
        pourInCup();
        addLemon();
    }
    public void boilWater(){
        System.out.println("将水煮沸");
    }
    public void steepTeaBag(){
        System.out.println("浸泡茶");
    }
    public void pourInCup(){
        System.out.println("将茶倒进杯子中");
    }
    public void addLemon(){
        System.out.println("加柠檬");
    }
}
public class Test{
    public static void main(String[] args) {
        Coffee coffee=new Coffee();
        coffee.prepareRecipe();
        Tea tea=new Tea();
        tea.prepareRecipe();
    }
}

我们可以发现茶和咖啡非常相似,因此可以将二者相同的部分抽取出来,放进一个基类(父类)当中。

我们可以把“将水煮沸”和“将饮料倒进杯子中”放到一个基类中。

我们在可以从冲泡法和往饮料中加调料着手,将二者的冲泡算法合并成

  1. 将水煮沸
  2. 用热水泡饮料
  3. 将饮料倒进杯子
  4. 在饮料内加入适当的调料

抽象基类代码实现:


//咖啡因饮料是一个抽象类
//抽象模板类
abstract class CaffeineBeverage{
    //使用同一个prepareBeverage()方法处理茶和咖啡
    //使用final关键字来防止子类覆盖这个方法
    //模板方法
    final void prepareRecipe(){
        boilWater();
        //冲饮料的方法
        brew();
        pourInCup();
        //往饮料中加的调料
        addCondiments();
    }
    //基本方法
    public void boilWater(){
        System.out.println("将水烧开");
    }
    //基本方法
    public void pourInCup(){
        System.out.println("将饮料倒进杯子");
    }
    //将咖啡和茶不同的处理方法声明为抽象,留给子类实现
    //基本方法
    public abstract void brew();
    //基本方法
    public abstract void addCondiments();
}
//具体模板类
class Coffee extends CaffeineBeverage{
    public void brew(){
        System.out.println("冲泡咖啡");
    }
    public void addCondiments(){
        System.out.println("加糖和牛奶");
    }
}
//具体模板类
class Tea extends CaffeineBeverage{
    public void brew(){
        System.out.println("冲泡茶");
    }
    public void addCondiments(){
        System.out.println("加柠檬");
    }
}
//场景类
public class Test{
    public static void main(String[] args) {
        //向上转型
        CaffeineBeverage coffee=new Coffee();
        coffee.prepareRecipe();
        //向上转型
        CaffeineBeverage tea=new Tea();
        tea.prepareRecipe();
    }
}

此时的类图:

模板方法定义了一个算法的步骤,并允许子类为一个或者多个步骤提供具体方法

使用模块方法所带来的优势

不好的茶或咖啡实现

模板方法提供的咖啡因饮料

Coffee或Tea主导一切,控制算法

由超类主导一切,它拥有算法,并且保护这个算法

Coffee与Tea之间存在重复代

有超类的存在,因此可以将代码复用最大化

对于算法所做的代码改变,需要打开各个子类修改很多地方

算法只存在一个地方,容易修改

弹性差,新种类的饮料加入需要做很多工作

弹性高,新饮料的加入只需要实现自己的冲泡和加料方 法即可

算法的知识和它的实现分散在许多类中

超类专注于算法本身,而由子类提供完整的实现。

钩子方法:是一类“默认不做事的方法”,子类可以选择性的覆盖他们

范例:扩展上述类,引入钩子方法

//咖啡因饮料是一个抽象类
abstract class CaffeineBeverage{
    //使用同一个prepareBeverage()方法处理茶和咖啡
    //使用final关键字来防止子类覆盖这个方法
    final void prepareRecipe(){
        boilWater();
        //冲饮料的方法
        brew();
        pourInCup();
        //判断顾客是否需要加入调料
        if(customerWantCondiments()){
            //往饮料中加的调料
            addCondiments();
        }
    }
    public void boilWater(){
        System.out.println("将水烧开");
    }
    public void pourInCup(){
        System.out.println("将饮料倒进杯子");
    }
    //将咖啡和茶不同的处理方法声明为抽象,留给子类实现
    public abstract void brew();
    public abstract void addCondiments();
    //钩子方法
    boolean customerWantCondiments(){
        return true;
    }
}
class Coffee extends CaffeineBeverage{
    public void brew(){
        System.out.println("冲泡咖啡");
    }
    public void addCondiments(){
        System.out.println("加糖和牛奶");
    }
}
class Tea extends CaffeineBeverage{
    public void brew(){
        System.out.println("冲泡茶");
    }
    public void addCondiments(){
        System.out.println("加柠檬");
    }
    public boolean customerWantCondiments(){
        String answer=getUserInput();
        if(answer.equals("y")){
            return true;
        }else{
            return false;
        }
    }
    //私有方法,获取用户的意愿
    private String getUserInput(){
        String answer=null;
        System.out.println("您想要在茶中加入柠檬吗(y/n)?");
        //写入用户的选择
        Scanner scanner=new Scanner(System.in);
        answer=scanner.nextLine();
        return answer;
    }
}
public class Test{
    public static void main(String[] args) {
        //向上转型
        CaffeineBeverage coffee=new Coffee();
        coffee.prepareRecipe();
        //向上转型
        CaffeineBeverage tea=new Tea();
        tea.prepareRecipe();
    }
}

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值