有了模板方法,你就可以像專家一樣復用代碼,同時保持對演算法的控制 ...
抽象方法和抽象類
抽象類:用abstract
修飾符修飾的類,如:
public abstract class GeneralService {
}
抽象方法:用abstract
修飾符修飾的方法,抽象方法不能有方法體,如:
public abstract void service();
抽象類和抽象方法的規則如下:
- 必須用
abstract
修飾符修飾 - 抽象類不一定包含抽象方法,但含有抽象方法的類一定是抽象類
- 抽象類不能被實例化
- 抽象類的構造器不能用於創建對象,主要是用於被其子類調用
下麵定義一個Shape抽象類:
/**
* 定義一個抽象類,用於描述抽象概念的“形狀”
*/
public abstract class Shape {
// 形狀的 顏色
private String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
// 帶參構造器
public Shape(String color) {
this.color = color;
}
// 定義一個計算周長的抽象方法
public abstract double calPerimeter();
}
上面的Shape類中包含了一個抽象方法calPerimeter()
,所以Shape類只能是抽象類。Shape類中既包含初始化塊,又包含構造器,不過這些都不是在創建Shape對象時被調用的,而是在創建其子類對象時被調用。
下麵定義一個Triangle類和一個Circle類,讓他們繼承Shape類,並實現Shape中的抽象方法calPerimeter()
。
/**
* 定義一個三角形類,繼承自形狀類
*/
public class Triangle extends Shape {
// 定義三角形的三條邊
private double a;
private double b;
private double c;
public Triangle(String color, double a, double b, double c) {
super(color);
this.a = a;
this.b = b;
this.c = c;
}
@Override
public double calPerimeter() {
return a + b + c;
}
}
/**
* 定義一個圓形類,繼承自形狀類
*/
public class Circle extends Shape {
// 定義圓的半徑
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double calPerimeter() {
return 2 * Math.PI * this.radius;
}
}
Shape(形狀)類是一個抽象的概念,Triangle(三角形)類和Circle(圓形)類是Shape的具象,它們都各自實現了Shape的calPerimeter()
方法,兩者計算周長的公式不一樣。
下麵是測試類:
/**
* 測試類
*/
public class Test {
public static void main(String[] args) {
Shape s1 = new Triangle("黃色", 3.0, 4.0, 5.0);
Shape s2 = new Circle("紅色", 3);
System.out.println("三角形s1的顏色:" + s1.getColor() + ",周長:" + s1.calPerimeter());
System.out.println("圓形s2的顏色:" + s2.getColor() + ",周長:" + s2.calPerimeter());
}
}
輸出結果:
三角形s1的顏色:黃色,周長:12.0
圓形s2的顏色:紅色,周長:18.84955592153876
- 當使用abstract修飾類時,表明這個類是抽象類,只能被繼承;當使用abstract修飾方法時,表明這個方法必須由其子類實現(重寫)。
- final修飾的類不能被繼承,final修飾的方法不能被重寫,因此final和abstract不能同時使用。
- 當使用static修飾一個方式時,表示這個方法是類方法,可以通過類直接調用而無需創建對象。但如果該方法被定義成抽象的,則將導致通過該類來調用該方法時出現錯誤(調用了一個沒有方法體的方法肯定會引起錯誤),因此,static和abstract不能同時修飾某個方法。
- abstract關鍵字修飾的方法必須由其子類重寫才有意義,因此abstract方法不能定義成private訪問許可權,即private和abstract不能同時修飾某個方法、
抽象類的作用
抽象類是從多個具體類中抽象出來的父類,它具有更高層次的抽象,描述了一組事物的共性。
抽象類作為多個子類的通用模板,子類在抽象類的基礎上進行擴展、改造,但子類總體上會大致保留抽象類的行為方式。
模板方法模式
如果編寫一個抽象父類,父類提供了多個子類的通用方法,並把一個或多個方法留給其子類去實現,這就是模板模式,是一種十分常見且簡單的設計模式。
稍微專業一點的定義就是:
模板方法模式,在一個方法中定義一個演算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以在不改變演算法結構的情況下,重新定義演算法中的某些步驟。
下麵再介紹一個模板方法模式的範例,在這個範例中,我們把做菜這個過程分為三個步驟:
- 備料
- 烹制
- 裝盤
這三部就是演算法的骨架。然而做不同的菜,需要備的料,烹制的方法,以及如何裝盤都是不同的,做不同的菜時,需要有不一樣的實現。
先來寫一個抽象的做菜父類,代碼如下:
/**
* 定義做菜抽象類
*/
public abstract class DodishTemplate {
/**
* 模板方法,封裝了做菜的演算法
* 用final關鍵字進行修飾,避免子類修改演算法的順序
* 模板方法定義了一連竄的步驟,每一個步驟由一個方法代表
*/
protected final void dodish(){
this.preparation();
this.doing();
this.sabot();
}
/**
* 備料
*/
public abstract void preparation();
/**
* 烹制
*/
public abstract void doing();
/**
* 裝盤
*/
public abstract void sabot();
}
下麵再定義做番茄炒蛋類和做紅燒肉類並實現父類中的抽象方法:
/**
* 做番茄炒蛋類
*/
public class EggsWithTomato extends DodishTemplate{
@Override
public void preparation() {
System.out.println("洗並切西紅柿,打雞蛋。");
}
@Override
public void doing() {
System.out.println("雞蛋倒入鍋里,然後倒入西紅柿一起炒。");
}
@Override
public void sabot() {
System.out.println("將炒好的番茄炒蛋裝入碟子里,撒上香蔥。");
}
}
/**
* 做紅燒肉類
*/
public class Bouilli extends DodishTemplate{
@Override
public void preparation() {
System.out.println("切豬肉和土豆。");
}
@Override
public void doing() {
System.out.println("將切好的豬肉倒入鍋中炒一會然後倒入土豆連炒帶燉。");
}
@Override
public void sabot() {
System.out.println("將做好的紅燒肉盛進碗里,撒上白芝麻");
}
}
在測試類中我們來做菜:
public class App {
public static void main(String[] args) {
DodishTemplate eggsWithTomato = new EggsWithTomato();
eggsWithTomato.dodish();
System.out.println("-----------------------------");
DodishTemplate bouilli = new Bouilli();
bouilli.dodish();
}
}
運行結果:
洗並切西紅柿,打雞蛋。
雞蛋倒入鍋里,然後倒入西紅柿一起炒。
將炒好的番茄炒蛋裝入碟子里,撒上香蔥。
-----------------------------
切豬肉和土豆。
將切好的豬肉倒入鍋中炒一會然後倒入土豆連炒帶燉。
將做好的紅燒肉盛進碗里,撒上白芝麻
從這個案例我們可以看到,DodishTemplate類里定義了做菜的通用演算法,而一些具體的實現細節則推遲到了其子類(EggsWithTomato和Bouilli)中。也就是說,模板方法定義了一個演算法的步驟,並允許子類為一個或多個步驟提供實現。