天天看點

20135313-exp2

北京電子科技學院(BESTI)

實     驗    報     告

課程:Java程式設計 班級:1353 姓名:吳子怡(20135313)
成績:           指導教師:婁嘉鵬  實驗日期:2015.5.5
實驗密級:   預習程度:   實驗時間:——
儀器組次: 13   必修/選修:選修  實驗序号:2
實驗名稱:            實驗二 Java面向對象程式設計

一、實驗内容

1. 初步掌握單元測試和TDD

2. 了解并掌握面向對象三要素:封裝、繼承、多态

3. 初步掌握UML模組化

4. 熟悉S.O.L.I.D原則

5. 了解設計模式

二、實驗要求

1.沒有Linux基礎的同學建議先學習《Linux基礎入門(新版)》《Vim編輯器》 課程

2.完成實驗、撰寫實驗報告,實驗報告以部落格方式發表,注意實驗報告重點是運作結果,遇到的問題(工具查找,安裝,使用,程式的編輯,調試,運作等)、解決辦法(空洞的方法如“查網絡”、“問同學”、“看書”等一律得0分)以及分析(從中可以得到什麼啟示,有什麼收獲,教訓等)。

3. 嚴禁抄襲,有該行為者實驗成績歸零,并附加其他懲罰措施。

4. 在電腦的目錄中用自己的學号建立一個目錄,代碼和UML圖要放到這個目錄中,截圖中沒有學号的會要求重做,然後跟着下面的步驟練習。

***如下為以學号建立的目錄截圖:

20135313-exp2

三、實驗儀器

名稱 型号 數量
PC  Dell 1
虛拟機 實驗樓

四、實驗步驟

(一)單元測試

(1) 三種代碼:當用程式解決問題時,要寫三種碼:僞代碼、産品代碼、測試代碼。

需求:我們要在一個MyUtil類中解決一個百分制成績轉成“優、良、中、及格、不及格”五級制成績的功能。

1、僞代碼(僞代碼可用漢語寫,推薦使用英語,僞代碼與具體程式設計語言無關,不要寫與具體程式設計語言文法相關的語句(如用malloc配置設定記憶體,這樣隻能用C語言程式設計了),僞代碼從意圖層面來解決問題,最終,僞代碼是産品代碼最自然的、最好的注釋。):

百分制轉五分制: 如果成績小于60,轉成“不及格” 如果成績在60與70之間,轉成“及格” 如果成績在70與80之間,轉成“中等” 如果成績在80與90之間,轉成“良好” 如果成績在90與100之間,轉成“優秀” 其他,轉成“錯誤” 

2、産品代碼: 有了僞代碼,再用特定程式設計語言翻譯,就得到可用的産品代碼。如用java語言編寫的代碼MyUtil.java。以下是我操作時的代碼截圖:

20135313-exp2

3、測試代碼:用于測試證明代碼沒有問題。Java程式設計時,程式員對類實作的測試叫單元測試(類XXXX的單元測試,一般建一個XXXXTest的類)。

針對MyUtil類,測試子產品為MyUtilTest.java,代碼截圖如下:

20135313-exp2

4、測試用例(Test Case):為某個特殊目标而編制的一組測試輸入、執行條件以及預期結果,以便測試某個程式路徑或核實是否滿足某個特定需求。

如本例中,測試輸入“50”,預期結果是“不及格”。在Eclipse中實驗運作結果截圖如下,測試結果符合預期:

20135313-exp2

隻有一組輸入的測試是不充分的,應将一般情況都測試一下。另外,

不能隻測試正常情況,還應測試異常情況,比如輸入為負分或大于100的成績。

實驗代碼和運作結果截圖如下:

20135313-exp2

運作程式發現負分時與期望運作結果不一緻,原因是判斷不及格時沒有要求成績大于零。通過修改MyUtil.java,增加對負分的判斷,實驗代碼和運作結果如下:

20135313-exp2

再次運作測試,測試結果符合預期,如下圖所示: 

20135313-exp2

測試邊界情況:對輸入為“0,60,70,80,90,100”這些邊界情況進行測試,實驗代碼及運作結果如下:

20135313-exp2

可發現邊界情況中輸入100時有一個Bug。修改MyUtil.java,把判斷優秀的條件中加入輸入為100的情況,修改後的代碼及運作結果截圖如下:

20135313-exp2

測試通過。

5、為保證單元測度是充分的,一般要求是測試代碼要比産品代碼多。為使子產品功能定義盡量明确,子產品内部的改變不會影響其他子產品,且子產品的品質能得到穩定的、量化的保證,單元測試就是一個很有效的解決方案。

(2) TDD(Test Driven Devlopment, 測試驅動開發):就是一種先寫測試代碼,然後再寫産品代碼的開發方法。

TDD的一般步驟如下:

  • 明确目前要完成的功能,記錄成一個測試清單
  • 快速完成編寫針對此功能的測試用例
  • 測試代碼編譯不通過(沒産品代碼呢)
  • 編寫産品代碼
  • 測試通過
  • 對代碼進行重構,并保證測試通過(重構下次實驗練習)
  • 循環完成所有功能的開發

1、實驗步驟:

打開Eclipse,單擊File->New->Java Project建立一個TDDDemo的Java項目,在TDDDemo項目中,把滑鼠放到項目名TDDDemo上,單擊右鍵,在彈出的菜單中標明New->Source Folder建立一個測試目錄test,把滑鼠放到test目錄上,單擊右鍵,在彈出的菜單中標明New->JUnit Test Case建立一個測試用例類MyUtilTest,增加第一個測試用例testNormal(注意測試用例前一定要有注解@Test,測試用例方法名任意)。

20135313-exp2
20135313-exp2

當測試代碼沒有文法錯誤時,我們把滑鼠放到MyUtilTest.java上,單擊右鍵,選擇Run as->JUnit Test。運作,若測試結果出現了一個紅條(red bar),說明測試沒通過,紅條上面會彙總測試情況。若測試結果出現一個綠條(green bar),說明測試通過了。TDD的目标是"Clean Code That Works",TDD的slogan是"Keep the bar green, to Keep the code clean"。

2、TDD的編碼節奏是:增加測試代碼,JUnit出現紅條;修改産品代碼;JUnit出現綠條,任務完成。

3、增加一個測試邊界情況的用例testBoundary,最終的完整版代碼及運作結果截圖如下:

20135313-exp2

(二)面向對象三要素

(1)抽象

抽象一詞的本意是指人在認識思維活動中對事物表象因素的舍棄和對本質因素的抽取。抽象是人類認識複雜事物和現象時經常使用的思維工具,抽象思維能力在程式設計中非常重要,"去粗取精、化繁為簡、由表及裡、異中求同"的抽象能力很大程度上決定了程式員的程式設計能力。

抽象就是抽出事物的本質特征而暫時不考慮他們的細節。對于複雜系統問題人們借助分層次抽象的方法進行問題求解;在抽象的最高層,可以使用問題環境的語言,以概括的方式叙述問題的解。在抽象的較低層,則采用過程化的方式進行描述。在描述問題解時,使用面向問題和面向實作的術語。 程式設計中,抽象包括兩個方面,一是過程抽象,二是資料抽象。

(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三要素的第一個要素是封裝,封裝就是将資料與相關行為包裝在一起以實作資訊就隐藏。

封裝實際上使用方法(method)将類的資料隐藏起來,控制使用者對類的修改和通路資料的程度,進而帶來子產品化(Modularity)和資訊隐藏(Information hiding)的好處;接口(interface)是封裝的準确描述手段。 

1、例如:

Java中用類進行封裝,比如一個Dog類,通過使用類和通路控制(private,public)隐藏了屬性color,開放了接口setColor(),getColor(),bark()和toString。Dog類是一個子產品。測試代碼與運作結果如下:

20135313-exp2

2、用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。

注意:(a)UML類圖要展示類之間的靜态關系,AnimalTest類依賴Dog類和Cat類,UML中依賴用帶箭頭的直線表示。

(b)UML類圖中繼承的表示法,是用一個帶三角的直線指向父類。

在模組化環境中的結果截圖如下:

20135313-exp2

Dog類和Cat類都有Color屬性和相應的setter和getter方法,明顯違反了前面提到的DRY原則,我們可以通過繼承解決這個問題,把Color屬性和相應的setter和getter方法放到父類Animal中。

通過繼承,消除了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類圖中的Animal類中的shout()方法是抽象方法,是斜體的,Animal類是抽象類,也是斜體的。

代碼表示及運作結果截圖如下:

20135313-exp2

這時getInfo隻需要一個了,參數為父類Animal,當方法參數類型為父類時,可以傳入子類的對象,如上面第6行所示。大家需要了解并記住“在Java中,當我們用父類聲明引用,用子類生成對象時,多态就出現了”,如上面第6行所示。

(三)設計模式初步

(1)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.

軟體實體(類,子產品,函數等)應該對擴充開放,對修改封閉。

基于OCP,利用面向對象中的多态性(Polymorphic),更靈活地處理變更代碼,OCP可以用以下手段實作:(1)抽象和繼承,(2)面向接口程式設計。 

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

子類必須可以被其基類所代

使用指向基類的指針或引用的函數,必須能夠在不知道具體派生類對象類型的情況下使用它

4、LSP的核心思想是父類型對象可以被子類型對象所取代。前面舉的Animal,Dog,Cat的那個例子是符合LSP原則的。LSP主張不要濫用繼承,LSP原則清楚地指出,OOD中“ISA關系”是就行為功能而言。行為功能(behavior)不是内在的、私有的,而是外在、公開的,是客戶程式所依賴的接口。

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

7、高層子產品不應該依賴于低層子產品。二者都應該依賴于抽象,抽象不應該依賴于細節。細節應該依賴于抽象 通過接口或者抽象類,DIP在應用中通過依賴注入的方式實作解耦,重用低級子產品,重用實作,解除依賴。

(2)模式與設計模式

模式是某外在環境(Context) 下﹐對特定問題(Problem)的慣用解決之道(Solution)。模式必須使得問題明晰,闡明為什麼用它來求解問題,以及在什麼情況下有用,什麼情況下不能起作用,每個模式因其重複性進而可被複用,本身有自己的名字,有可傳授性,能移植到不同情景下。模式可以看作對一個問題可複用的專家級解決方法。 計算機科學中有很多模式:GRASP模式、分析模式 、軟體體系結構模式 、設計模式:建立型,結構型,行為型、管理模式: The Manager Pool 實作模式、界面設計互動模式 …

這裡面最重要的是設計模式,在面向對象中設計模式的地位可以和面向過程程式設計中的資料結構的地位相當。

(3)設計模式實示例:設計模式(design pattern)提供一個用于細化軟體系統的子系統或元件,或它們之間的關系圖,它描述通信元件的公共再現結構,通信元件可以解決特定語境中的一個設計問題。 随着系統中對象的數量增多,對象之間的互動成指數增長,設計模式可以幫我們以最好的方式來設計系統。設計模式背後是抽象和SOLID原則。 設計模式有四個基本要素:

Pattern name:描述模式,便于交流,存檔

Problem:描述何處應用該模式

Solution:描述一個設計的組成元素,不針對特例

Consequence:應用該模式的結果和權衡(trade-offs)

1、Java類庫中大量使用設計模式:

Factory:java.util.Calendar

Compsite:java.awt.Container

Decorator:java I/0

Iterator:java.util.Enumeration

Strategy:java.awt.LayoutManager 

2、首先設計一個文檔:

20135313-exp2

如果要求系統支援Float類,這是一個合理的要求,要支援Float類,Document類要修改兩個地方,這違反了OCP原則,使用多态可以解決部分問題:

20135313-exp2

要支援Float類,Document類要修改構造方法,這還違反了OCP原則。封裝、繼承、多态解決不了問題,則采用設計模式解決問題:

20135313-exp2
20135313-exp2

通過增加了一層抽象層使代碼符合了OCP原則。代碼有良好的可擴充性、可維護性,代價是代碼多了,效率變低下了。 DRY原則和YAGNI原則并非完全相容。前者追求"抽象化",要求找到通用的解決方法;後者追求"快和省",意味着不要把精力放在抽象化上面,因為很可能"你不會需要它"。為了謀求平衡,可以采用Rule of three (三次原則):第一次用到某個功能時,寫一個特定的解決方法;第二次用到的時候,拷貝上一次的代碼(違反了DRY);第三次出現的時候,才着手"抽象化",寫出通用的解決方法。

除SOLID原則外還有很多其它的面向對象原則。如:

"組合替代繼承":這是說相對于繼承,要更傾向于使用組合;

"笛米特法則":這是說"你的類對其它類知道的越少越好";

"共同封閉原則":這是說"相關類應該打包在一起";

"穩定抽象原則":這是說"類越穩定,越應該由抽象類組成";

原則并不是孤立存在的,而是緊密聯系的,設計模式是這些原則在一些特定場景的應用結果。是以,可以把設計模式看作"架構",把OOD原則看作"規範"。

(四)練習:使用TDD的方式設計關實作複數類Complex。

(1)設計成果

1、僞代碼:

* 定義一個複數類:複數類内包含:

* 1、複數的生成:由設定實部虛部、擷取實部虛部,拼接合成複數整體(傳回複數整體)三部分組成;

* 2、複數的四則運算:均由實部與實部運算,虛部與虛部運算(傳回新複數);

* 3、運算模式選擇:包含輸出語句,無傳回值。

2、産品代碼:

20135313-exp2
20135313-exp2
20135313-exp2

3、測試代碼

20135313-exp2
20135313-exp2

4、運作結果:

20135313-exp2

(2)PSP(Personal Software Process)

步驟 耗時 百分比
需求分析  10min  16.7%
設計
代碼實作  25min  41.6%
測試  5min  8.3%
分析總結

3、總結單元測試的好處:讓代碼維護更容易;有助于改進代碼品質和設計;保證最後的代碼修改不會破壞之前代碼的功能;提升回報速度,減少重複工作,提高開發效率;幫助開發人員編寫代碼,提升品質、減少bug。

五、遇到的問題與解決方法

當做實驗第一部分的第二個子產品(TDD)時,我直接在實驗二的Java project中建立了part1的一個包,裡面包含MyUtil和MyUtilTest兩個類。第二步在同一個工程中建立測試用例類exp2_part2_TDDDemo_Test,在其下建立包exp2_part2,然後選擇建立JUnit Test Case。因為我和實驗示範步驟不同,沒有将TDD示範建立在另一個工程中,且我為了解決.java檔案中沒有MyUtil.java的問題,我在代碼首部導入了exp2_part1這個包,用于調用這個包中的其他.java檔案。這樣一來卻沒有得到示範中的錯誤提示,反而是一次性通過測試了。後來才發現是因為示範代碼中MyUtil.java中隻有return “錯誤”;這個語句,而我調用的代碼已經在實驗一中做到基本完善了。

另外,在編寫最後的練習題時,我在代碼書寫中花了比較多的時間,雖然寫了僞代碼以後讓程式設計思路清晰了不少,但是在寫代碼時還是會遇到一些細節性問題,也展現出我在之前的視訊學習中對一些知識點的遺忘比較多,根據Eclipse中的調試和提示,我最後修正了代碼,同時也檢視了一些成品代碼,發現他們在讀入實部虛部時的設定、擷取函數的編寫和我的設計思路的差别,引發了我對設計的思考,以後我會多看看成品代碼再動手寫寫代碼,先從整體上學會一些程式設計格式和思路,再去實踐,少走一些彎路。

六、實驗收獲

1、單元測試的操作和優點

2、模組化的整體感悟

3、調試代碼的經驗和設計架構的總體優化

4、對Eclipse的熟練程度有些許提高

5、對設計模式有了初步了解

 七、參考資料

1.《UML精粹》 2.《建構之法 (電子版)》,著者鄒欣Blog 3.《深入淺出設計模式》 4.《解析極限程式設計》 5.《單元測試之道》

  1. 《代碼大全》
  2. 《代碼的抽象三原則》

八、工具

JUnit

umbrello

StarUML