標籤:
北京電子科技學院(BESTI)
實 驗 報 告
課程:Java程式設計 班級:1352 姓名:王國伊 學號:20135207
成績: 指導教師:婁嘉鵬 實驗日期:2015.5.8
實驗密級:無 預習程度: 實驗時間:15:30~18:00
儀器組次:07 必修/選修:選修 實驗序號:02
實驗名稱: Java物件導向程式設計
實驗目的與要求: 1. 初步掌握單元測試和TDD; 2. 理解並掌握物件導向三要素:封裝、繼承、多態; 3. 初步掌握UML建模; 4. 熟悉S.O.L.I.D原則; 5. 瞭解設計模式。
實驗儀器:
|
名稱 |
型號 |
數量 |
|
PC機 |
Lenovo Z485 |
1 |
|
Linux虛擬機器 |
Ubuntu(32bit) |
1 |
實驗步驟
一、單元測試
- 三種代碼
編程是智力活動,不是打字,編程前要把幹什麼、如何幹想清楚才能把程式寫對、寫好。當我們想用程式解決問題時,要會寫三種碼:
需求:我們要在一個MyUtil類中解決一個百分製成績轉成“優、良、中、及格、不及格”五級製成績的功能。
虛擬碼:
產品代碼:
測試代碼:
經過幾次修改後得到可以通過自己測試的代碼,即能夠實現需求的代碼。
這時測試都符合預期了,我們把MyUtil.java提供給別人使用時,他人可以按照要求實現需求。為保證單元測度是充分的,我們的一般要求是測試代碼要比產品代碼多。
2.TDD(Test Driven Devlopment, 測試驅動開發)
TDD的一般步驟如下:
- 明確當前要完成的功能,記錄成一個測試清單
- 快速完成編寫針對此功能的測試案例
- 測試代碼編譯不通過(沒產品代碼呢)
- 編寫產品代碼
- 測試通過
- 對代碼進行重構,並保證測試通過(重構下次實驗練習)
- 迴圈完成所有功能的開發
基於TDD,我們不會出現過度設計的情況,需求通過測試案例表達出來了,我們的產品代碼只要讓測試通過就可以了。 Java中有單元測試工具JUnit來輔助進行TDD,我們用TDD的方式把前面百分制轉五分制的例子重寫一次,體會一下有測試載入器支援的開發的好處。
具體操作如下:
開啟Eclipse,單擊File->New->Java Project建立一個TDDDemo
在彈出的菜單中選定New->Source Folder建立一個測試目錄test
我們把滑鼠放到test目錄上,單擊右鍵,在彈出的菜單中選定New->JUnit Test Case建立一個測試案例類MyUtilTest
我們增加第一個測試案例testNormal,注意測試案例前一定要有註解@Test,測試案例方法名任意
圖中的紅叉說明代碼存在語法錯誤,原因很簡單,MyUtil類還不存在,類中的percentage2fivegrade方法也不存在,我們在TDDDemo的src目錄中建立一個MyUtil的類,並實現percentage2fivegrade方法,如所示:
測試結果出現了一個紅條(red bar),說明測試沒通過,
測試結果出現了一個綠條(green bar),說明測試通過了。
TDD的編碼節奏是:
- 增加測試代碼,JUnit出現紅條
- 修改產品代碼
- JUnit出現綠條,任務完成
二、物件導向三要素
1.抽象
抽象一詞的本意是指人在認識思維活動中對事物表象因素的捨棄和對本質因素的抽取。抽象是人類認識複雜事物和現象時經常使用的思維工具,抽象思維能力在程式設計中非常重要,"去粗取精、化繁為簡、由表及裡、異中求同"的抽象能力很大程度上決定了程式員的程式設計能力。
抽象就是抽出事物的本質特徵而暫時不考慮他們的細節。對於複雜系統問題人們藉助分層次抽象的方法進行問題求解;在抽象的最高層,可以使用問題環境的語言,以概括的方式敘述問題的解。在抽象的較低層,則採用過程化的方式進行描述。在描述問題解時,使用面向問題和面向實現的術語。程式設計中,抽象包括兩個方面,一是過程抽象,二是資料抽象。
我們舉個例子說明一下。比如有了以下Java代碼:
System.out.println(1); System.out.println(2); System.out.println(3);
可以列印出“1,2,3”,想打引“1,2,3,4”怎麼辦?同學們的做法大多是把上面的代碼拷貝下來,再加一行:
System.out.println(1); System.out.println(2); System.out.println(3); System.out.println(4);
這就是沒有學會過程抽象的做法“拷貝粘貼”式開發。解決問題沒?解決了,但有問題,比如想列印出“1..100"怎麼辦?粘貼100行?這兩段代碼有三行重複的代碼,違反了常見的一個編程原則DRY(Don‘t Repeat Yourself),解決的方法是進行過程抽象,寫一個函數printn:
public void printn(int n){ for(int i=1; i<=n; i++) System.out.println(n); }
上面兩段代碼就可以用;
printn(3); printn(4);
代替了,列印出“1..100"也很簡單,只要調用printn(100);就行了。
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的核心和起源。
OO三要素的第一個要素是封裝,封裝就是將資料與相關行為封裝在一起以實現資訊就隱藏。Java中用類進行封裝,比如一個Dog類:
封裝實際上使用方法(method)將類的資料隱藏起來,控制使用者對類的修改和訪問資料的程度,從而帶來模組化(Modularity)和資訊隱藏(Information hiding)的好處;介面(interface)是封裝的準確描述手段。 Dog類通過使用類和存取控制(private,public)隱藏了屬性color,開放了介面setColor(),getColor(),bark()和toString。Dog類是一個模組,我們可以通過下面的代碼使用它,測試代碼與運行結果如下:
我們可以用UML中的類圖來描述類Dog,首先我們在實驗樓的環境中開啟shell,在命令列中輸入umbrello,開啟UML建模軟體umbrello,如所示:
先單擊工具列上的類表徵圖,再在class diagram(類圖)中單擊一下,會彈出一個聖誕框,輸入類名Dog, 我們把滑鼠放到Dog類上,單擊右鍵,選擇Properties,在彈出的對話方塊中的Display中去掉Public Only選項,我們把滑鼠放到Dog類上,單擊右鍵,選擇New->Attribute,在彈出的對話方塊中的填好Type,Name,並選好Visibility,我們把滑鼠放到Dog類上,單擊右鍵,選擇New->Operation,在彈出的對話方塊中的填好Type,Name,並選好Visibility,我們可以看到,在UML 裡,一個類的屬效能顯示它的名字,類型,初始化值,屬性也可以顯示private,public,protected。 類的方法能顯示它們的方法名,參數,傳回型別,以及方法的private,public,protected屬性。其中:
- +表示public
- #表示 protected
- -表示 private
使用UML可以讓我們不必關注細節。同樣,我們可以建立一個Cat類這時的測試類別如以下UML圖所示:
注意:UML類圖要展示類之間的靜態關係,AnimalTest類依賴Dog類和Cat類,UML中依賴用帶箭頭的直線表示。 對應的測試代碼和運行結果如所示:
我們看到Dog類和Cat類都有Color屬性和相應的setter和getter方法,明顯違反了前面提到的DRY原則,我們可以通過繼承解決這個問題,把Color屬性和相應的setter和getter方法放到父類Animal中,如以下UML較圖所示:
UML類圖中繼承的標記法,是用一個帶三角的直線指向父類,通過繼承,我們消除了Dog類和Cat類中的重複代碼,符合DRY的要求。 繼承指一個類的定義可以基於另外一個已經存在的類,即子類基於父類,從而實現父類代碼的重用。既存類稱作基類、超類、父類(base class、super class、parent class),新類稱作衍生類別、繼承類、子類(derived class、inherited class、child class)。繼承關係表達了”Is a kind of“的關係,稱為“ISA”關係。繼承的關鍵在於確認子類為父類的一個特殊類型。繼承是實現軟體可重用的根基,是提高軟體系統的可擴充性與可維護性的主要途徑。 如上面所示,以封裝為基礎,繼承可以實現代碼複用,需要注意的是,繼承更重要的作用是實現多態。物件導向中允許不同類的對象對同一訊息做出響應,即同一訊息可以根據發送對象的不同而採用多種不同的行為方式,我們稱此現象為多態性。Java中,多態是指不同的類對象調用同一個簽名的成員方法時將執行不同代碼的現象。多態是物件導向程式設計的靈活性和可擴充性的基礎。我們再看看上一個類圖,我們可以進一步抽象,把Dog類中的bark()和Cat類中的meow()抽象成一個抽象方法shout(),Dog類和Cat類中覆蓋這個方法,如以下UML圖所示:
UML類圖中的Animal類中的shout()方法是抽象方法,是斜體的,Animal類是抽象類別,也是斜體的。 對應的代碼及運行結果如下:
在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,依賴倒置原則)
1).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)面向介面編程。
使用者現大需要一個Triangle模組是一個合理的要求,由於我們使用了多態,原先的模組不需要改變,只要新增加一個模組Triangle就可以了,如所示:
這個圖形系統是符合OCP原則的。
2).SRP的內容是:
- There should never be more than one reason for a class to change
- 決不要有一個以上的理由修改一個類
3).LSP的內容是:
- Subtypes must be substitutable for their base types
- Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it
- 子類必須可以被其基類所代
- 使用指向基類的指標或引用的函數,必須能夠在不知道具體衍生類別物件類型的情況下使用它(LSP是Liskov女士提出的)
4).LSP的核心思想是父類型對象可以被子類型對象所取代。我們前面舉的Animal,Dog,Cat的那個例子是符合LSP原則的。
5).ISP的內容是:
- Clients should not be forced to depend upon interfaces that they do not use
- 客戶不應該依賴他們並未使用的介面
6).DIP的內容是:
- High level modules should not depend upon low level modules. Both should depend upon abstractions
- Abstractions should not depend upon details. Details should depend upon abstractions
- 高層模組不應該依賴於低層模組。二者都應該依賴於抽象
- 抽象不應該依賴於細節。細節應該依賴於抽象
2. 模式與設計模式
模式是某外在環境(Context) 下﹐對特定問題(Problem)的慣用解決之道(Solution)。模式必須使得問題明晰,闡明為什麼用它來求解問題,以及在什麼情況下有用,什麼情況下不能起作用,每個模式因其重複性從而可被複用,本身有自己的名字,有可傳授性,能移植到不同情景下。模式可以看作對一個問題可複用的專家級解決方案。電腦科學中有很多模式:
- GRASP模式
- 分析模式
- 軟體體繫結構模式
- 設計模式:建立型,結構型,行為型
- 管理員模式: The Manager Pool 實現模式
- 介面設計互動模式
- …
這裡面最重要的是設計模式,在物件導向中設計模式的地位可以和面向過程編程中的資料結構的地位相當。
3.設計模式實樣本
設計模式四個基本元素
Pattern name:描述模式,便於交流,存檔
Problem:描述何處應用該模式
Solution:描述一個設計的組成元素,不針對特例
Consequence:應用該模式的結果和權衡
四、使用TDD的方式設計關實現複數類Complex
步驟如下:
1、編寫計算複數類Complex,分為加減乘除四種情況。
public class Complex {
private double m;
private double n;
public Complex(double m,double n) {
this.m = m;
this.n = n;
}
public String add(Complex b) {
Complex x = new Complex(m + b.m, n + b.n);
System.out.println("a" + "+" + "b" + "=" + x);
return "true";
}
public String minus(Complex b) {
Complex x = new Complex(m - b.m, n - b.n);
System.out.println("a" + "-" + "b" + "=" + x);
return "true";
}
public String multiply(Complex b) {
Complex x = new Complex(m * b.m - n * b.n, m*b.n + n * b.m);
System.out.println("a" + "*" + "b" + "=" + x);
return "true";
}
public String divide(Complex b) {
double d = Math.sqrt(b.m * b.m) + Math.sqrt(b.n * b.n);
Complex x = new Complex((m * b.m + n * b.n) / d,Math.round((m * b.n - n * b.m)/d));
System.out.println("a" + "/" + "b" + "=" + x);
return "true";
}
public String toString() {
String str = "";
if(n>0)
str = "(" + m + "+" + n + "i" + ")";
if(n==0)
str = "(" + m + ")";
if(n<0)
str = "(" + m + n + "i" +")";
return str;
}
}
2、編寫ComplexDemo類,傳入a、b兩個複數的實部和虛部,調用Complex類進行計算。
實驗二Java物件導向程式設計