鄭朝霞
摘要:在使用Java語(yǔ)言開發(fā)軟件過(guò)程中,好的軟件產(chǎn)品必須具有可擴(kuò)展性好、可維護(hù)性及可復(fù)用性,也就是軟件系統(tǒng)要符合開閉原則的。該文討論了在軟件開發(fā)中如何引入接口來(lái)實(shí)現(xiàn)多態(tài),從而使軟件系統(tǒng)滿足開閉原則。
關(guān)鍵詞:開閉原則;Java語(yǔ)言;多態(tài)性
中圖分類號(hào):TP311 文獻(xiàn)標(biāo)識(shí)碼:A 文章編號(hào):1009-3044(2016)30-0262-03
1 背景
在學(xué)習(xí)JAVA程序設(shè)計(jì)的過(guò)程中,經(jīng)常會(huì)看到這樣的話語(yǔ):正是由于接口和抽象類的存在才賦予java強(qiáng)大的面向?qū)ο蟮哪芰?。但?duì)于初學(xué)者來(lái)說(shuō),要理解抽象類和接口的語(yǔ)法很容易,要理解抽象類和接口的本質(zhì)作用卻比較難。如果我們結(jié)合面向?qū)ο蟮脑O(shè)計(jì)模式中的開閉原則來(lái)理解接口和抽象類的作用,則會(huì)比較透徹、容易理解。1988年,Bertrand Meyer在他的著作《Object Oriented Software Construction》
中提出了開閉原則(OCP,Open-Closed Principle),開閉原則的描述比較簡(jiǎn)單,具體如下:Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.軟件實(shí)體(類,模塊,方法等等)應(yīng)當(dāng)對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉。其中open for extension(對(duì)擴(kuò)展開放)指的是當(dāng)出現(xiàn)新需求的時(shí)候,可以通過(guò)擴(kuò)展現(xiàn)有軟件實(shí)體達(dá)到目的。開閉原則中“open”,是指軟件組件功能的擴(kuò)展是開放的,允許對(duì)其進(jìn)行功能擴(kuò)展。”Close for modification”(對(duì)修改關(guān)閉)指的是不允許對(duì)該實(shí)體做任何修改,“close”是指對(duì)于原有代碼的修改是封閉的,即不應(yīng)該修改原有的代碼。也就是說(shuō),需要執(zhí)行多樣行為的軟件實(shí)體應(yīng)該設(shè)計(jì)成不需要修改就可以實(shí)現(xiàn)各種需求的變化,堅(jiān)持開閉原則可以大大減輕軟件項(xiàng)目維護(hù)的工作量。
開閉原則的核心就是:軟件實(shí)體應(yīng)當(dāng)對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉。
實(shí)現(xiàn)開閉原則的關(guān)鍵就在于“抽象”,而不是基于“實(shí)現(xiàn)”來(lái)編程。下面我們來(lái)看看一個(gè)基于“實(shí)現(xiàn)”編程的例子。
假如我們要實(shí)現(xiàn)這樣一個(gè)需求:爸爸喝綠茶。通過(guò)這個(gè)需求,我們分析其中包含兩個(gè)類:爸爸類和綠茶類。兩個(gè)類之間的關(guān)系如下:
用JAVA語(yǔ)言實(shí)現(xiàn)的代碼如下:
public class GreenTea {
private String color;
private String teaName;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getTeaName() {
return teaName;
}
public void setTeaName(String teaName) {
this.teaName = teaName;
}
public String use() {
return "綠茶是未經(jīng)發(fā)酵制成的茶,因此較多地保留了鮮葉的天然物質(zhì).沖泡綠茶時(shí),水溫控制在80℃~90℃左右...";
}
}
class Daddy {
public void drink(GreenTea gTea) {
System.out.println("爸爸喜歡喝" +gTea.getName());
System.out.println(gTea.use());
}
}
public class DrinkTest {
public static void main(String[] args) {
Daddy dad = new Daddy();
GreenTea gt = new GreenTea();
gt.setName(“綠茶”);
gt.setColor(“紅茶”);
System.out.println(“我是”+gt.getName()+”,顏色為:”+gt.getColor);
dad.drink(gt);
}
}
在上述代碼中,我們可以看到,這是基于實(shí)現(xiàn)來(lái)進(jìn)行的編程,Daddy類是進(jìn)行業(yè)務(wù)邏輯處理的,屬于高層模塊,GreenTea類是低層模塊,負(fù)責(zé)基本的原子操作。在這段代碼中,Daddy類依賴GreenTea類,也就是高層模塊依賴低層模塊,我們看看會(huì)帶來(lái)什么樣的問題。
在實(shí)際的軟件開發(fā)中,需求總是在發(fā)生變化,假如需求變成這樣:爸爸因?yàn)槲覆缓?,改喝紅茶了。紅茶的代碼如下:
class BlackTea {
public String use() {
return "紅茶是一種發(fā)酵而成的茶,其沖泡方法更是豐富多彩,玻璃杯沖泡法、瓷杯沖泡法......";
}
}
現(xiàn)在我們就會(huì)發(fā)現(xiàn),必須要修改負(fù)責(zé)業(yè)務(wù)邏輯處理的Daddy類,爸爸才能喝紅茶,如果爸爸的需求不斷發(fā)生變化,我們就得不斷的修改Daddy類,很顯然上述代碼不符合開閉原則。
而在實(shí)際的軟件開發(fā)中,修改業(yè)務(wù)邏輯處理的高層模塊將會(huì)帶來(lái)風(fēng)險(xiǎn),引入錯(cuò)誤,對(duì)于后期的軟件開發(fā)和維護(hù)帶來(lái)很多的問題。
我們必須修改設(shè)計(jì),讓它符合開閉原則。
2 開閉原則的應(yīng)用
上述代碼顯然不符合開閉原則,因?yàn)檫M(jìn)行業(yè)務(wù)邏輯處理的Daddy類和GreenTea類的耦合度太高,需要降低它們之間的耦合度!那么如何才能降低耦合度。
解決該問題的最關(guān)鍵因素在于抽象化,在編輯語(yǔ)言當(dāng)中,系統(tǒng)可以將抽象設(shè)計(jì)定義為較為固定的設(shè)計(jì)方式,利用Java語(yǔ)言,可以定義多個(gè)接口??梢詳U(kuò)展實(shí)現(xiàn)類,而抽象層都不會(huì)改變,這就進(jìn)一步使得開閉原則的第二條得到滿足,對(duì)修改進(jìn)行關(guān)閉。此外,系統(tǒng)的行為可以通過(guò)從抽象層導(dǎo)出多個(gè)新的實(shí)現(xiàn)類而改變。由于系統(tǒng)設(shè)計(jì)是開放的,這與開閉原則的第一條存在著高度的一致性。
在這里我們?cè)O(shè)計(jì)了一個(gè)三層的結(jié)構(gòu),通過(guò)引入接口和抽象類來(lái)實(shí)現(xiàn)開閉原則。第一層是接口ITea,定義了一個(gè)use()方法;第二層是抽象類Tea,實(shí)現(xiàn)了ITea接口;第三層是具體的實(shí)現(xiàn)類,可以擴(kuò)展很多個(gè)。
接口ITea只定義子類應(yīng)該實(shí)現(xiàn)的方法use(),而沒有實(shí)現(xiàn)公有的功能。在ITea接口中只有一個(gè)方法use(),專門進(jìn)行茶的處理, 由所有的實(shí)現(xiàn)類實(shí)現(xiàn)該方法。同時(shí) ITea作為接口應(yīng)該是穩(wěn)定且可靠的,不應(yīng)該經(jīng)常發(fā)生變化,否則接口做為契約的作用就失去了效能。
引入了抽象類Abstrac class Tea,實(shí)現(xiàn)了公有的功能setColor()、getColor()、setName()、getName,但并沒有實(shí)現(xiàn)use()方法,use()針對(duì)抽象茶類進(jìn)行編程,由具體的實(shí)現(xiàn)類分別去實(shí)現(xiàn)各自的use()方法。并通過(guò)構(gòu)造方法由客戶端來(lái)設(shè)置實(shí)例化的具體茶對(duì)象。如果需要增加一種新的茶,如咖啡Coffee,只需要將Coffee也作為Tea的子類,無(wú)須修改現(xiàn)有類庫(kù)的源代碼。
現(xiàn)階段在大部分的軟件在開發(fā)的過(guò)程當(dāng)中,都定義了實(shí)現(xiàn)類的接口,通過(guò)這種接口的定義,只需要改變一個(gè)實(shí)現(xiàn)類就可以實(shí)現(xiàn)所有實(shí)體的轉(zhuǎn)變。
通過(guò)引入接口和抽象類,就可以讓負(fù)責(zé)業(yè)務(wù)邏輯處理的Daddy類依賴抽象類Tea,而不是依賴實(shí)現(xiàn)類GreenTea類、BlackTea類。也就是高層模塊應(yīng)該依賴抽象,而不是具體的實(shí)現(xiàn)。如圖所示,各子類由接口類定義了接口方法,只需要在不同的子類中編寫不同的實(shí)現(xiàn)即可,當(dāng)然也可以擴(kuò)展自有的方法。
修改后的設(shè)計(jì)如下:
修改設(shè)計(jì)后的實(shí)現(xiàn)代碼如下:
這個(gè)是接口ITea,主要定義方法use()
public interface ITea {
public String use();
}
這個(gè)是抽象類Tea,抽象類Tea實(shí)現(xiàn)了接口ITea
public abstract class Tea implements ITea{
private String color;
private String teaName;
public ITea(String teaName, String color) {
this.teaName = teaName;
this.color = color;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getTeaName() {
return teaName;
}
public void setTeaName(String teaName) {
this.teaName = teaName;
}
public abstract String use();
}
這個(gè)是實(shí)現(xiàn)類GreenTea,繼承了抽象類Tea, 實(shí)現(xiàn)了use()方法。我們可以擴(kuò)展很多類似的實(shí)現(xiàn)類。
public class GreenTea extends Tea {
public String use() {
return "綠茶是未經(jīng)發(fā)酵制成的茶,因此較多地保留了鮮葉的天然物質(zhì).沖泡綠茶時(shí),水溫控制在80℃~90℃左右...";
}
}
擴(kuò)展的實(shí)現(xiàn)類紅茶類BlackTea,同樣也是繼承了抽象類Tea,實(shí)現(xiàn)了use()方法。我們不需要修改其他的設(shè)計(jì),只需要擴(kuò)展實(shí)現(xiàn)類就可以了。
class BlackTea extends Tea {
public String use() {
return "紅茶是一種發(fā)酵而成的茶,其沖泡方法更是豐富多彩,玻璃杯沖泡法、瓷杯沖泡法......";
}
}
這個(gè)是業(yè)務(wù)邏輯處理類Daddy,依賴于抽象類Tea.
class Daddy {
public void drink(Tea tea) {
System.out.println("爸爸喜歡喝"+tea.getTeaName()+"!");
System.out.println("顏色為:"+tea.getColor()+"!");
System.out.println(tea.use());
}
}
下面的代碼主要為了測(cè)試軟件的功能,是不是實(shí)現(xiàn)了開閉原則。
public class DrinkTest {
public static void main(String[] args) {
Daddy dad = new Daddy();
GreenTea gt = new GreenTea(“綠茶”,”綠色”);
BlackTea bt = new BlackTea(“紅茶”,”褐色”);
dad.drink(gt);
dad.drink(bt);
}
}
上面的程序是利用Java語(yǔ)言的多態(tài)性來(lái)實(shí)現(xiàn)的軟件開閉原則。多態(tài)性是指程序中定義的父類引用變量所指向的對(duì)象和該引用變量所進(jìn)行的方法調(diào)用在代碼編寫時(shí)并不確定,該引用變量所指向的對(duì)象往往都是在程序運(yùn)行的過(guò)程當(dāng)中才決定的,以及該變量的調(diào)用究竟是在哪個(gè)子類當(dāng)中所實(shí)現(xiàn)的,同時(shí)這也必須是要在程序運(yùn)行的過(guò)程當(dāng)中才可以實(shí)現(xiàn)的。這是由于具體的子類是在程序運(yùn)行的過(guò)程當(dāng)中才被決定下來(lái)的,通過(guò)這種方式,源代碼并不需要 經(jīng)過(guò)修改就可以被直接實(shí)現(xiàn),在這個(gè)過(guò)程當(dāng)中調(diào)用的具體方法也會(huì)隨著被改變,同時(shí)程序在運(yùn)行過(guò)程當(dāng)中所綁定的代碼也會(huì)被隨著改變,同時(shí)該程序也可以以多個(gè)狀態(tài)運(yùn)行,這就是所謂的多態(tài)性,正是因?yàn)榫哂卸鄳B(tài)性才導(dǎo)致軟件具有較強(qiáng)的擴(kuò)展性與靈活性。
在Daddy類中,drink(Tea tea)中父類類型(Tea)的引用將指向子類的對(duì)象(GreenTea、BlackTea等等),在程序運(yùn)行的過(guò)程中,根據(jù)指向的不同實(shí)現(xiàn)類,將調(diào)用不同的子類對(duì)象的use()方法。GreenTea、BlackTea等實(shí)現(xiàn)類繼承抽象類Tea,并實(shí)現(xiàn)了Tea的use()方法。由于GreenTea、BlackTea實(shí)現(xiàn)了use()方法,那么父類類型的引用gt在調(diào)用該方法時(shí)將會(huì)調(diào)用子類中重寫的use()方法。
也就是說(shuō),如果爸爸的需求不斷發(fā)生變化,我們不需要修改進(jìn)行業(yè)務(wù)邏輯處理的Daddy類,只需要擴(kuò)展子類。如果爸想喝茉莉花茶,我們就擴(kuò)展一個(gè)JasmineTea類,增加新的子類不影響已存在類的多態(tài)性、繼承性,以及其他特性的運(yùn)行和操作。
很顯然上述代碼體現(xiàn)了JAVA語(yǔ)言的多態(tài)性,軟件設(shè)計(jì)符合開閉原則。
3 結(jié)束語(yǔ)
大多數(shù)的程序設(shè)計(jì)都需要與閉合原則存在一定的一致性,開閉原則也是對(duì)不同模塊進(jìn)行評(píng)價(jià)的基本依據(jù),我們可以利用開閉原則來(lái)判斷系統(tǒng)的擴(kuò)展性與靈活性。
絕大部分的設(shè)計(jì)模式都必須符合開閉原則,在對(duì)每一個(gè)模式進(jìn)行優(yōu)缺點(diǎn)評(píng)價(jià)時(shí)都會(huì)以開閉原則作為一個(gè)重要的評(píng)價(jià)依據(jù),以判斷基于該模式設(shè)計(jì)的系統(tǒng)是否具備良好的靈活性和可擴(kuò)展性。
所以,我們?cè)趯?shí)際的軟件開發(fā)中,需要通過(guò)擴(kuò)展來(lái)實(shí)現(xiàn)業(yè)務(wù)邏輯的變化,而不是修改。如果一個(gè)軟件系統(tǒng)符合開閉原則的,那么從軟件工程的角度來(lái)看,它至少具有這樣的好處:可擴(kuò)展性好、可維護(hù)性好。
參考文獻(xiàn);
[1] 薩默維爾.軟件工程[M]. 程成, 譯. 9版.北京: 機(jī)械工業(yè)出版社, 2011.
[2] Metsker S J.Java設(shè)計(jì)模式[M]. 2版.北京: 電子工業(yè)出版社, 2012.
[3] 趙亞娟. 計(jì)算機(jī)軟件JAVA編程特點(diǎn)及其技術(shù)研究[J]. 數(shù)字技術(shù)與應(yīng)用, 2016(1): 113.
[4] 葛萌, 張琳娜, 陳偉. Java多態(tài)性機(jī)制應(yīng)用研究[J]. 攀枝花學(xué)院學(xué)報(bào), 2016(2): 25-28.
[5] 羅詩(shī)敏. 基于MVC的學(xué)生信息管理系統(tǒng)的分析與設(shè)計(jì)[D]. 廣州: 華南理工大學(xué), 2014