抽象类的定义和使用
抽象类的使用原则
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
如果构造方法,那么对象中的属性一定都是其对应数据类型的默认值
对象实例化过程:
进行类加载
进行类对象的空间开辟
进行类对象的属性初始化(构造方法)
关于内部抽象类:子类只需要覆写外部抽象类的直接抽象方法。内部抽象类的抽象方法可不覆写。外部抽象类中不能使用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();
}
}
模板模式举例·:
星巴克咖啡冲泡法
星巴克茶冲泡法
根据冲泡步骤使用代码实现咖啡和茶的类:
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();
}
}
我们可以发现茶和咖啡非常相似,因此可以将二者相同的部分抽取出来,放进一个基类(父类)当中。
我们可以把“将水煮沸”和“将饮料倒进杯子中”放到一个基类中。
我们在可以从冲泡法和往饮料中加调料着手,将二者的冲泡算法合并成
抽象基类代码实现:
//咖啡因饮料是一个抽象类
//抽象模板类
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();
}
}