標籤:
課程:Java實驗 班級:201352 姓名:程涵 學號:20135210
成績: 指導教師:婁佳鵬 實驗日期:15.05.05
實驗密級: 預習程度: 實驗時間:
儀器組次: 必修/選修:選修 實驗序號:2
實驗名稱: Java物件導向程式設計
實驗目的與要求:
1. 初步掌握單元測試和TDD
2. 理解並掌握物件導向三要素:封裝、繼承、多態
3. 初步掌握UML建模
4. 熟悉S.O.L.I.D原則
5. 瞭解設計模式
實驗要求
1.沒有Linux基礎的同學建議先學習《Linux基礎入門(新版)》《Vim編輯器》 課程
2.完成實驗、撰寫實驗報告,實驗報告以部落格方式發表在部落格園,注意實驗報告重點是運行結果,遇到的問題(工具尋找,安裝,使用,程式的編輯,調試,運行等)、解決辦法(空洞的方法如“查網路”、“問同學”、“看書”等一律得0分)以及分析(從中可以得到什麼啟示,有什麼收穫,教訓等)。報告可以參考範飛龍老師的指導
3. 嚴禁抄襲,有該行為者實驗成績歸零,並附加其他懲罰措施。
實驗儀器:
實驗內容、步驟與體會(附紙):
(一)單元測試
(1) 三種代碼
編程是智力活動,不是打字,編程前要把幹什麼、如何幹想清楚才能把程式寫對、寫好。與目前不少同學一說編程就開啟編輯器寫代碼不同,我希望同學們養成一個習慣,當你們想用程式解決問題時,要會寫三種碼:
我們通過一個例子說明如何寫這三種代碼。
需求:我們要在一個MyUtil類中解決一個百分製成績轉成“優、良、中、及格、不及格”五級製成績的功能。
我們設計了一個測試案例(Test Case),測試案例是為某個特殊目標而編製的一組測試輸入、執行條件以及預期結果,以便測試某個程式路徑或核實是否滿足某個特定需求。這裡我們的測試輸入是“50”,預期結果是“不及格”。在Eclipse中運行結果如下,測試結果符合預期:
只有一組輸入的測試是不充分的,我們把一般情況都測試一下,
在Eclipse中運行結果如下,測試結果符合預期:
我們不能只測試正常情況,下面看看異常情況如何,比如輸入為負分或大於100的成績,
運行程式發現負分時與期望不一致,終於找到了一個bug,原因是判斷不及格時沒有要求成績大於零。我們修改MyUtil.java,增加對負分的判斷再次運行測試,測試結果符合預期,如所示:
測試夠了嗎?還不夠,一般代碼在邊界處最容易出錯,我們還沒有測試邊界情況,我們對輸入為“0,60,70,80,90,100”這些邊界情況進行測試,
測試結果如下:
我們發現邊界情況中輸入100時有一個Bug。我們修改MyUtil.java,把判斷優秀的條件中包含輸入為100的情況,
這時測試都符合預期了,我們把MyUtil.java提供給別人使用時,心裡比較有底氣了。那如何保證單元測度是充分的呢?我們的一般要求是測試代碼要比產品代碼多。如何寫測試,《單元測試之道》提出了Right-BICEP的方法,大家可以參考一下。
軟體是由多人合作完成的,不同人員的工作相互有依賴關係。軟體的很多錯誤都來源於程式員對模組功能的誤解、疏忽或不瞭解模組的變化。如何能讓自己負責的模組功能定義盡量明確,模組內部的改變不會影響其他模組,而且模組的品質能得到穩定的、量化的保證?單元測試就是一個很有效解決方案
(2) TDD(Test Driven Devlopment, 測試驅動開發)
先寫測試代碼,然後再寫產品代碼的開發方法叫“測試驅動開發”(TDD)。
TDD的一般步驟如下:
- 明確當前要完成的功能,記錄成一個測試清單
- 快速完成編寫針對此功能的測試案例
- 測試代碼編譯不通過(沒產品代碼呢)
- 編寫產品代碼
- 測試通過
- 對代碼進行重構,並保證測試通過(重構下次實驗練習)
- 迴圈完成所有功能的開發
基於TDD,我們不會出現過度設計的情況,需求通過測試案例表達出來了,我們的產品代碼只要讓測試通過就可以了。
Java中有單元測試工具JUnit來輔助進行TDD,我們用TDD的方式把前面百分制轉五分制的例子重寫一次,體會一下有測試載入器支援的開發的好處。
開啟Eclipse,單擊File->New->Java Project建立一個TDDDemo的Java項目,
我們在TDDDemo項目中,把滑鼠放到項目名TDDDemo上,單擊右鍵,在彈出的菜單中選定New->Source Folder建立一個測試目錄test,如:
我們把滑鼠放到test目錄上,單擊右鍵,在彈出的菜單中選定New->JUnit Test Case建立一個測試案例類MyUtilTest,如:
我們增加第一個測試案例testNormal,注意測試案例前一定要有註解@Test,測試案例方法名任意,輸入以下代碼:
import org.junit.Test;import junit.framework.TestCase;public class MyUtilTest extends TestCase {@Testpublic void testNormal() {assertEquals("不及格", MyUtil.percentage2fivegrade(55));assertEquals("及格", MyUtil.percentage2fivegrade(65));assertEquals("中等", MyUtil.percentage2fivegrade(75));assertEquals("良好", MyUtil.percentage2fivegrade(85));assertEquals("優秀", MyUtil.percentage2fivegrade(95));}}
輸入完畢,Eclipse中如所示:
圖中的紅叉說明代碼存在語法錯誤,原因很簡單,MyUtil類還不存在,類中的percentage2fivegrade方法也不存在,我們在TDDDemo的src目錄中建立一個MyUtil的類,並實現percentage2fivegrade方法,如所示:
大家可以看到現在測試代碼沒有語法錯誤了,我們把滑鼠放到MyUtilTest.java上,單擊右鍵,選擇Run as->JUnit Test,如:
測試結果出現了一個紅條(red bar),說明測試沒通過,紅條上面匯總了測試情況,運行了一個測試,沒有錯誤,一個測試沒通過。下面原因說的也很清楚:測試代碼第十行傳入55時,期望結果是“不及格”,代碼返回了“錯誤”,修改MyUtil.Java吧,輸入以下代碼:
再運行測試,如所示:
測試結果出現了一個綠條(green bar),說明測試通過了。
TDD的目標是"Clean Code That Works",TDD的slogan是"Keep the bar green, to Keep the code clean",大家體會一下。
TDD的編碼節奏是:
- 增加測試代碼,JUnit出現紅條
- 修改產品代碼
- JUnit出現綠條,任務完成
我們增加一個測試異常情況的用例testException,
我們增加一個測試邊界情況的用例testBoundary,
如何讓JUnit的gree bar出來,動手實驗一下,如:
不管用不用TDD,寫出高品質的測試案例才是最重要的,如何進行單元測試,大家可參考一下《單元測試之道》這本書。另外,《Agile Java 中文版》展示了如何將Java和TDD進行有效整合,通過TDD驅動項目開發,有興趣的可以參考。
(2)封裝、繼承與多態
物件導向(Object-Oriented)的三要素包括:封裝、繼承、多態。物件導向的思想涉及到軟體開發的各個方面,如物件導向分析(OOA)、物件導向設計(OOD)、物件導向編程實現(OOP)。OOA根據抽象關鍵的問題域來分解系統,關注是什麼(what)。OOD是一種提供符號設計系統的物件導向的實現過程,用非常接近問題域術語的方法把系統構造成“現實世界”的對象,關注怎麼做(how),通過模型來實現功能規格。OOP則在設計的基礎上用程式設計語言(如Java)編碼。貫穿OOA、OOD和OOP的主線正是抽象。
OOD中建模會用圖形化的建模語言UML(Unified Modeling Language),UML是一種通用的建模語言,我們實驗中使用umbrello進行建模,Windows中推薦大家使用 StarUML。
過程抽象的結果是函數,資料抽象的結果是抽象資料類型(Abstract Data Type,ADT),類可以作具有繼承和多態機制的ADT。資料抽象才是OOP的核心和起源。
封裝實際上使用方法(method)將類的資料隱藏起來,控制使用者對類的修改和訪問資料的程度,從而帶來模組化(Modularity)和資訊隱藏(Information hiding)的好處;介面(interface)是封裝的準確描述手段。
- +表示public
- #表示 protected
- -表示 private
繼承指一個類的定義可以基於另外一個已經存在的類,即子類基於父類,從而實現父類代碼的重用。既存類稱作基類、超類、父類(base class、super class、parent class),新類稱作衍生類別、繼承類、子類(derived class、inherited class、child class)。繼承關係表達了”Is a kind of“的關係,稱為“ISA”關係。繼承的關鍵在於確認子類為父類的一個特殊類型
。繼承是實現軟體可重用的根基,是提高軟體系統的可擴充性與可維護性的主要途徑。
如上面所示,以封裝為基礎,繼承可以實現代碼複用,需要注意的是,繼承更重要的作用是實現多態。
物件導向中允許不同類的對象對同一訊息做出響應,即同一訊息可以根據發送對象的不同而採用多種不同的行為方式,我們稱此現象為多態性。Java中,多態是指不同的類對象調用同一個簽名的成員方法時將執行不同代碼的現象。多態是物件導向程式設計的靈活性和可擴充性的基礎。
(三)設計模式初步(1)S.O.L.I.D原則
物件導向三要素是“封裝、繼承、多態”,任何物件導向程式設計語言都會在文法上支援這三要素。如何藉助抽象思維用好三要素特別是多態還是非常困難的,S.O.L.I.D類設計原則是一個很好的指導:
- SRP(Single Responsibility Principle,單一職責原則)
- OCP(Open-Closed Principle,開放-封閉原則)
- LSP(Liskov Substitusion Principle,Liskov替換原則)
- ISP(Interface Segregation Principle,介面分離原則)
- DIP(Dependency Inversion Principle,依賴倒置原則)
OCP是OOD中最重要的一個原則,OCP的內容是:
- software entities (class, modules, function, etc.) should open for extension,but closed for modification.
- 軟體實體(類,模組,函數等)應該對擴充開放,對修改封閉。
對擴充開放(Open For Extension )要求軟體模組的行為必須是可以擴充的,在應用需求改變或需要滿足新的應用需求時,我們要讓模組以不同的方式工作; 對修改封閉(Closed for Modification )要求模組的原始碼是不可改動的,任何人都不許修改已有模組的原始碼。 基於OCP,利用物件導向中的多態性(Polymorphic),更靈活地處理變更擁抱變化,OCP可以用以下手段實現:(1)抽象和繼承,(2)面向介面編程。
(2)模式與設計模式
模式是某外在環境(Context) 下﹐對特定問題(Problem)的慣用解決之道(Solution)。模式必須使得問題明晰,闡明為什麼用它來求解問題,以及在什麼情況下有用,什麼情況下不能起作用,每個模式因其重複性從而可被複用,本身有自己的名字,有可傳授性,能移植到不同情景下。模式可以看作對一個問題可複用的專家級解決方案。 電腦科學中有很多模式:
- GRASP模式
- 分析模式
- 軟體體繫結構模式
- 設計模式:建立型,結構型,行為型
- 管理員模式: The Manager Pool 實現模式
- 介面設計互動模式
- …
這裡面最重要的是設計模式,在物件導向中設計模式的地位可以和面向過程編程中的資料結構的地位相當。
(3)設計模式實樣本
設計模式(design pattern)提供一個用於細化軟體系統的子系統或組件,或它們之間的關係圖,它描述通訊組件的公用再現結構,通訊組件可以解決特定語境中的一個設計問題。
我們看到通過增加了一層抽象層使代碼符合了OCP原則。代碼有良好的可擴充性、可維護性,代價是代碼多了,效率變低下了。
設計模式初學者容易過度使用它們,導致過度設計,也就是說,遵守DRY和OCP當然好,但會出現YAGNI(You aren‘t gonna need it, 你不會需要它)問題。
DRY原則和YAGNI原則並非完全相容。前者追求"抽象化",要求找到通用的解決方案;後者追求"快和省",意味著不要把精力放在抽象化上面,因為很可能"你不會需要它"。怎麼平衡呢?有一個Rule of three (三次原則):第一次用到某個功能時,你寫一個特定的解決方案;第二次又用到的時候,你拷貝上一次的代碼(違反了DRY);第三次出現的時候,你才著手"抽象化",寫出通用的解決方案。
設計模式學習先參考一下《深入淺出設計模式》,這本書可讀性非常好。
除SOLID原則外還有很多其它的物件導向原則。如:
- "組合替代繼承":這是說相對於繼承,要更傾向於使用組合;
- "笛米特法則":這是說"你的類對其它類知道的越少越好";
- "共同封閉原則":這是說"相關類應該打包在一起";
- "穩定抽象原則":這是說"類越穩定,越應該由抽象類別組成";
當然,這些原則並不是孤立存在的,而是緊密聯絡的,遵循一個原則的同時也就遵循了另外一個或多個原則;反之,違反了其中一個原則也很可能同時就違反了另外一個或多個原則。 設計模式是這些原則在一些特定情境的應用結果。因此,可以把設計模式看作"架構",把OOD原則看作"規範"。 在學習設計模式的過程中,要經常性的反思,這個設計模式體現了物件導向設計原則中的哪個或哪一些原則。
(四)練習1使用TDD的方式設計關實現複數類Complex。
虛擬碼:
無輸入 --- 則複數的實部為0,虛部為0
僅輸入實部 --- 複數的實部為所輸入的實部,虛部為0
僅輸入虛部 --- 複數的實部為0,虛部為所輸入的虛部
實部虛部都輸入 --- 複數的實部與虛部對應相應輸入的實部虛部。
加法 --- 複數的實部與實部相加,虛部與虛部相加 ,即輸出p1.rePart+p2.rePart,p1.imPart+p2.imPart
減法 --- 複數的實部與實部相減,虛部與虛部相減 , 即輸出 p1.rePart-p2.rePart,p1.imPart-p2.imPart
產品代碼:
package Exercise;
public class Complex
{
double rePart,imPart;
Complex()
{
this.rePart=0;
this.imPart=0;
}
Complex(double rePart)
{
this.rePart=rePart;
this.imPart=0;
}
Complex(double rePart,double imPart)
{
this.rePart=rePart;
this.imPart=imPart;
}
Complex Jia(Complex p1,Complex p2)
{
Complex p =new Complex(p1.rePart+p2.rePart,p1.imPart+p2.imPart);
return p;
}
Complex Jian(Complex p1,Complex p2)
{
Complex p =new Complex(p1.rePart-p2.rePart,p1.imPart-p2.imPart);
return p;
}
void Print()
{
System.out.println("複數的值為:");
if(this.imPart!=0)
System.out.println(this.rePart+"+"+this.imPart+"i");
else System.out.println(this.rePart);
}
}
測試代碼:
package Exercise;
public class Test
{
public static void main(String[] args)
{
Complex c=new Complex();
Complex c1=new Complex(2,7);
Complex c2=new Complex(5,2);
c1.Print();
c2.Print();
System.out.println("這兩複數和為:");
System.out.println((c.Jia(c1, c2).rePart+"+"+c.Jia(c1, c2).imPart+"i").toString());
System.out.println("這兩複數差為:");
System.out.println(c.Jian(c1, c2).rePart+"+"+c.Jian(c1, c2).imPart+"i");
}
}
2.實驗報告中統計自己的PSP(Personal Software Process)時
步驟 |
耗時 |
百分比 |
需求分析 |
5 |
6.25% |
設計 |
10 |
12.5% |
代碼實現 |
50 |
62.5% |
測試 |
10 |
12.5% |
分析總結 |
5 |
6.25%
|
3. 實現要有虛擬碼,產品代碼,測試代碼。4.總結單元測試的好處
1.加快項目進程,並且是程式的設計更加完善。
2.通過簡單的交易回復功能在生產環境上做基於真實資料的測試而不用擔心會產生不必要的資料。
3. 讓自己負責的模組功能定義明確,模組與模組之間內部的改變不會產生影響。
實驗二- Java物件導向程式設計