日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

【Java】《面向对象程序设计——Java语言》Castle代码修改整理

發(fā)布時(shí)間:2025/3/15 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【Java】《面向对象程序设计——Java语言》Castle代码修改整理 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

最近閑來無事刷刷MOOC,找到以前看的浙大翁凱老師的《面向?qū)ο蟪绦蛟O(shè)計(jì)——Java語言》課程,重新過一遍仍覺受益頗深。
其中有一個(gè)Castle的例子,思路很Nice但代碼很爛,翁凱老師在后面幾章不斷地帶領(lǐng)觀看者修改這個(gè)代碼,那我也大概整理一下這部分的內(nèi)容吧。

原版代碼

即使類的設(shè)計(jì)很糟糕,也還是有可能實(shí)現(xiàn)一個(gè)應(yīng)用程序,使之運(yùn)行并完成所需的工作。一個(gè)已完成的應(yīng)用程序能夠運(yùn)行,但并不能表明程序內(nèi)部的結(jié)構(gòu)是否良好。當(dāng)維護(hù)程序員想要對(duì)一個(gè)已有的軟件做修改的時(shí)候,問題才會(huì)浮現(xiàn)出來。比如,程序員試圖糾正已有軟件的缺陷,或者為其增加一些新的功能。顯然,如果類的設(shè)計(jì)良好,這個(gè)任務(wù)就可能很輕松;而如果類的設(shè)計(jì)很差,那就會(huì)變得很困難,要牽扯大量的工作。在大的應(yīng)用軟件中,這樣的情形在最初的實(shí)現(xiàn)中就會(huì)發(fā)生了。如果以不好的結(jié)構(gòu)來實(shí)現(xiàn)軟件,那么后面的工作可能變得很復(fù)雜,整個(gè)程序可能根本無法完成,或者充滿缺陷,或者花費(fèi)比實(shí)際需要多得多的時(shí)間才能完成。在現(xiàn)實(shí)中,一個(gè)公司通常要維護(hù)、擴(kuò)展和銷售一個(gè)軟件很多年,很可能今天在商店買到的軟件,其最初的版本是在十多年前就開始了的。在這種情形下,任何軟件公司都不能忍受不良結(jié)構(gòu)的代碼。既然很多不良設(shè)計(jì)的效果會(huì)在試圖調(diào)整或擴(kuò)展軟件時(shí)明顯地展現(xiàn)出來,那么就應(yīng)該以調(diào)整或擴(kuò)展軟件來鑒別和發(fā)現(xiàn)這樣的不良設(shè)計(jì)。

這里將使用一個(gè)叫作城堡游戲的例子,這個(gè)例子很簡(jiǎn)單,基本實(shí)現(xiàn)了一個(gè)基于字符的探險(xiǎn)游戲。起初這個(gè)游戲并不十分強(qiáng)大,因?yàn)檫€沒全部完成,你可以運(yùn)用你的想像力來設(shè)計(jì)和實(shí)現(xiàn)這個(gè)的游戲,讓它更有趣更好玩……

那么,首先,從下邊這個(gè)糟糕的代碼開始吧!

Room類

package castle;public class Room {public String description;public Room northExit;public Room southExit;public Room eastExit;public Room westExit;public Room(String description) {this.description = description;}public void setExits(Room north, Room east, Room south, Room west) {if(north != null)northExit = north;if(east != null)eastExit = east;if(south != null)southExit = south;if(west != null)westExit = west;}@Overridepublic String toString(){return description;} }

Game類

import java.util.Scanner;public class Game {private Room currentRoom;public Game() {createRooms();}private void createRooms(){Room outside, lobby, pub, study, bedroom;// 制造房間outside = new Room("城堡外");lobby = new Room("大堂");pub = new Room("小酒吧");study = new Room("書房");bedroom = new Room("臥室");// 初始化房間的出口outside.setExits(null, lobby, study, pub);lobby.setExits(null, null, null, outside);pub.setExits(null, outside, null, null);study.setExits(outside, bedroom, null, null);bedroom.setExits(null, null, null, study);currentRoom = outside; // 從城堡門外開始}private void printWelcome() {System.out.println();System.out.println("歡迎來到城堡!");System.out.println("這是一個(gè)超級(jí)無聊的游戲。");System.out.println("如果需要幫助,請(qǐng)輸入 'help' 。");System.out.println();System.out.println("現(xiàn)在你在" + currentRoom);System.out.print("出口有:");if(currentRoom.northExit != null)System.out.print("north ");if(currentRoom.eastExit != null)System.out.print("east ");if(currentRoom.southExit != null)System.out.print("south ");if(currentRoom.westExit != null)System.out.print("west ");System.out.println();}// 以下為用戶命令private void printHelp() {System.out.print("迷路了嗎?你可以做的命令有:go bye help");System.out.println("如:\tgo east");}private void goRoom(String direction) {Room nextRoom = null;if(direction.equals("north")) {nextRoom = currentRoom.northExit;}if(direction.equals("east")) {nextRoom = currentRoom.eastExit;}if(direction.equals("south")) {nextRoom = currentRoom.southExit;}if(direction.equals("west")) {nextRoom = currentRoom.westExit;}if (nextRoom == null) {System.out.println("那里沒有門!");}else {currentRoom = nextRoom;System.out.println("你在" + currentRoom);System.out.print("出口有: ");if(currentRoom.northExit != null)System.out.print("north ");if(currentRoom.eastExit != null)System.out.print("east ");if(currentRoom.southExit != null)System.out.print("south ");if(currentRoom.westExit != null)System.out.print("west ");System.out.println();}}public static void main(String[] args) {Scanner in = new Scanner(System.in);Game game = new Game();game.printWelcome();while ( true ) {String line = in.nextLine();String[] words = line.split(" ");if ( words[0].equals("help") ) {game.printHelp();} else if (words[0].equals("go") ) {game.goRoom(words[1]);} else if ( words[0].equals("bye") ) {break;}}System.out.println("感謝您的光臨。再見!");in.close();}}

熟悉代碼

  • 這個(gè)應(yīng)用程序的任務(wù)是什么? 通過接受用戶的輸入,改變當(dāng)前所在的位置。
  • 這個(gè)應(yīng)用程序接受什么樣的命令? help、go、bye。
  • 每個(gè)命令做什么? help:顯示使用說明;go:切換當(dāng)前的房子;bye:退出應(yīng)用程序。
  • 在場(chǎng)景中有多少間房? 大堂、酒吧、書房、臥室 共四間房子。
  • 畫出現(xiàn)有的房間的地圖:小酒吧----城堡外------大堂|書房-----------臥室
  • 說說代碼的問題

    沒法說,太多了……看后面咋改吧。。。

    消除代碼重復(fù)

    程序中存在相似甚至相同的代碼塊,是非常低級(jí)的代碼質(zhì)量問題。

    代碼復(fù)制存在的問題是,如果需要修改一個(gè)副本,那么就必須同時(shí)修改所有其他的副本,否則就存在不一致的問題。這增加了維護(hù)程序員的工作量,而且存在造成錯(cuò)誤的潛在危險(xiǎn)。很可能發(fā)生的一種情況是,維護(hù)程序員看到一個(gè)副本被修改好了,就以為所有要修改的地方都已經(jīng)改好了。因?yàn)闆]有任何明顯跡象可以表明另外還有一份一樣的副本代碼存在,所以很可能會(huì)遺漏還沒被修改的地方。

    我們從消除代碼復(fù)制開始。消除代碼復(fù)制的兩個(gè)基本手段,就是函數(shù)和父類。

    代碼復(fù)制是不良設(shè)計(jì)的一種表現(xiàn),而上面的代碼中并不少見,比如:

    System.out.println("現(xiàn)在你在" + currentRoom); System.out.print("出口有:"); if(currentRoom.northExit != null)System.out.print("north "); if(currentRoom.eastExit != null)System.out.print("east "); if(currentRoom.southExit != null)System.out.print("south "); if(currentRoom.westExit != null)System.out.print("west "); System.out.println();

    處理方式就是單獨(dú)封裝成一個(gè)函數(shù):

    public void showPrompt() {System.out.println("現(xiàn)在你在" + currentRoom);System.out.print("出口有:");if(currentRoom.northExit != null)System.out.print("north ");if(currentRoom.eastExit != null)System.out.print("east ");if(currentRoom.southExit != null)System.out.print("south ");if(currentRoom.westExit != null)System.out.print("west ");System.out.println(); }

    注意可擴(kuò)展性

    可擴(kuò)展性也是必須注意的事情,簡(jiǎn)單的講就是“面對(duì)未來未知的變化,能夠以不變應(yīng)萬變,以最小的代價(jià)和最小的影響來擁抱變化”(非官方的說法,個(gè)人覺得更容易理解)。
    可運(yùn)行的代碼≠良好的代碼,雖代碼做維護(hù)的時(shí)候更能看出代碼的質(zhì)量(無論是自己維護(hù)還是交給他人維護(hù))。

    比如說上面的代碼,我們?cè)赗oom類中用的是north、south、east、west,如果要加入up、down,則不僅要改Room,還要改Game,而且是大改,這樣影響程序的可擴(kuò)展性、可維護(hù)性。

    做好封裝

    要評(píng)判某些設(shè)計(jì)比其他的設(shè)計(jì)優(yōu)秀,就得定義一些在類的設(shè)計(jì)中重要的術(shù)語,以用來討論 設(shè)計(jì)的優(yōu)劣。對(duì)于類的設(shè)計(jì)來說,有兩個(gè)核心術(shù)語:耦合聚合耦合這個(gè)詞指的是類和類之間的聯(lián)系。之前的章節(jié)中提到過,程序設(shè)計(jì)的目標(biāo)是一系列通過定義明確的接口通信來協(xié)同工作的類。耦合度反映了這些類聯(lián)系的緊密度。我們努力要獲得低的耦合度,或者叫作松耦合(loose coupling)。

    耦合度決定修改應(yīng)用程序的容易程度。在一個(gè)緊耦合的結(jié)構(gòu)中,對(duì)一個(gè)類的修改也會(huì)導(dǎo)致對(duì)其他一些類的修改。這是要努力避免的,否則,一點(diǎn)小小的改變就可能使整個(gè)應(yīng)用程序發(fā)生改變。另外,要想找到所有需要修改的地方,并一一加以修改,卻是一件既困難又費(fèi)時(shí)的事情。另一方面,在一個(gè)松耦合的系統(tǒng)中,常常可以修改一個(gè)類,但同時(shí)不會(huì)修改其他類,而且整個(gè)程序還可以正常運(yùn)作。

    聚合與程序中一個(gè)單獨(dú)的單元所承擔(dān)的任務(wù)的數(shù)量和種類相對(duì)應(yīng)有關(guān),它是針對(duì)類或方法這樣大小的程序單元而言的。理想情況下,一個(gè)代碼單元應(yīng)該負(fù)責(zé)一個(gè)聚合的任務(wù)(也就是說,一個(gè)任務(wù)可以被看作是一個(gè)邏輯單元)。一個(gè)方法應(yīng)該實(shí)現(xiàn)一個(gè)邏輯操作,而一個(gè)類應(yīng)該代表一定類型的實(shí)體。聚合理論背后的要點(diǎn)是重用:如果一個(gè)方法或類是只負(fù)責(zé)一件定義明確的事情,那么就很有可能在另外不同的上下文環(huán)境中使用。遵循這個(gè)理論的一個(gè)額外的好處是,當(dāng)程序某部分的代碼需要改變時(shí),在某個(gè)代碼單元中很可能會(huì)找到所有需要改變的相關(guān)代碼段。

    當(dāng)然,以上面的代碼為例,其余細(xì)節(jié)暫且不論,把屬性設(shè)置成public,直接訪問,這完全不符合封裝的原則。
    再細(xì)說一下這里的封裝問題:Room和Game都有大量代碼和出口相關(guān),尤其是Room的四大屬性,這樣的設(shè)計(jì)大大加強(qiáng)了耦合度,不利于維護(hù)。

    那是不是用“初學(xué)OOP經(jīng)典大法”——[private]屬性+[public]getter方法?
    比如說public Room northExit;改成:

    public Room getNorth() {return this.northExit; }

    其實(shí)真不是,這真的是很多人的一個(gè)誤區(qū)。
    誠(chéng)然,寫setter/getter比起public的屬性已經(jīng)好了很多,但你細(xì)品,持有引用的類還是需要知道被引用的類的細(xì)節(jié),二者還是緊緊耦合在一起的。
    那我們需要什么呢?
    我們需要這樣一個(gè)函數(shù):

    public String getExitDesc() {StringBuilder sb = new StringBuilder();if (this.northExit != null) {sb.append("north ");}if (this.southExit != null) {sb.append("south ");}if (this.eastExit != null) {sb.append("east ");}if (this.westExit != null) {sb.append("west ");}return sb.toString(); }

    在此基礎(chǔ)上,我們也知道之前為了避免代碼重復(fù)而寫了這樣一個(gè)方法:

    public void showPrompt() {System.out.println("現(xiàn)在你在" + currentRoom);System.out.print("出口有:");if(currentRoom.northExit != null)System.out.print("north ");if(currentRoom.eastExit != null)System.out.print("east ");if(currentRoom.southExit != null)System.out.print("south ");if(currentRoom.westExit != null)System.out.print("west ");System.out.println(); }

    我們?yōu)榱私档婉詈隙?#xff0c;需要將其改為:

    public void showPrompt() {System.out.println("現(xiàn)在你在" + currentRoom);System.out.print("出口有:");System.out.println(currentRoom.getExitDesc());System.out.println(); }

    接著看,之前由于去重代碼,goRoom()已經(jīng)是這個(gè)樣子了:

    private void goRoom(String direction) {Room nextRoom = null;if(direction.equals("north")) {nextRoom = currentRoom.northExit;}if(direction.equals("east")) {nextRoom = currentRoom.eastExit;}if(direction.equals("south")) {nextRoom = currentRoom.southExit;}if(direction.equals("west")) {nextRoom = currentRoom.westExit;}if (nextRoom == null) {System.out.println("那里沒有門!");} else {currentRoom = nextRoom;showPrompt();} }

    但其實(shí)這里還是重度耦合,我們要將這個(gè)事交還給Room來做:

    public Room getExit(String direction) {Room nextRoom = null;if(direction.equals("north")) {nextRoom = this.northExit;}if(direction.equals("east")) {nextRoom = this.eastExit;}if(direction.equals("south")) {nextRoom = this.southExit;}if(direction.equals("west")) {nextRoom = this.westExit;}return nextRoom; }

    而goRoom()則變成了:

    private void goRoom(String direction) {Room nextRoom = currentRoom.getExit(direction);if (nextRoom == null) {System.out.println("那里沒有門!");} else {currentRoom = nextRoom;showPrompt();} }

    至此,Room和Game之間的耦合度大大降低了,至少?zèng)]了直接的屬性調(diào)用,Game不必完全知道Room的細(xì)節(jié)了。

    使用接口增強(qiáng)可擴(kuò)展性

    上面的修改完成之后還有哪些不足呢?
    上面的代碼修改針對(duì)Room類實(shí)現(xiàn)的新方法,雖說把方向的細(xì)節(jié)正是隱藏在Room內(nèi)部了,今后方向如何實(shí)現(xiàn)也與外部無關(guān)了,但還是一種“硬編碼”的方式。
    Game與Room松耦合,但Room本身還是“硬編碼”,一旦方向變化,則需要大量的重寫代碼,可擴(kuò)展性還是不好。
    那怎么處理呢?
    答案是:使用集合容器,比如HashMap。

    修改方法就是刪去所有的屬性,轉(zhuǎn)而換成一個(gè)Map屬性:

    private Map<String , Room> exits = new HashMap<>();

    這么改可還行,問題是之前的全被推翻了,那就重寫唄!
    比如說這個(gè)方法:

    public void setExits(Room north, Room east, Room south, Room west) {if(north != null)northExit = north;if(east != null)eastExit = east;if(south != null)southExit = south;if(west != null)westExit = west; }

    肯定是不能要了,那就重寫一個(gè)getExit():

    public void setExit(String dir, Room room) {exits.put(dir, room); }

    同樣地,之前有一個(gè)修改后加進(jìn)去的方法:

    public String getExitDesc() {StringBuilder sb = new StringBuilder();if (this.northExit != null) {sb.append("north ");}if (this.southExit != null) {sb.append("south ");}if (this.eastExit != null) {sb.append("east ");}if (this.westExit != null) {sb.append("west ");}return sb.toString(); }

    也是涉及方向細(xì)節(jié),要改:

    public String getExitDesc() {StringBuilder sb = new StringBuilder();for (Entry entry : exists.entrySet()) {sb.append(entry.getKey()).append(' ');}return sb.toString(); }

    上一次重寫的getExit()也要改:

    public Room getExit(String direction) {return exits.get(direction); }

    需要說明的是,永遠(yuǎn)不要認(rèn)為這樣一行代碼的方法沒有存在的意義,因?yàn)檫@最關(guān)鍵的是表示一個(gè)接口,提供這種服務(wù),如果以后不這么寫了呢?對(duì)吧,大家都是聰明人,不必多言。

    Game類也受到點(diǎn)“波及”:

    private void createRooms() {Room outside, lobby, pub, study, bedroom;// 制造房間outside = new Room("城堡外");lobby = new Room("大堂");pub = new Room("小酒吧");study = new Room("書房");bedroom = new Room("臥室");// 初始化房間的出口outside.setExits(null, lobby, study, pub);lobby.setExits(null, null, null, outside);pub.setExits(null, outside, null, null);study.setExits(outside, bedroom, null, null);bedroom.setExits(null, null, null, study);currentRoom = outside; // 從城堡門外開始 }

    這里要改,但很簡(jiǎn)單,反正不過是初始化而已,調(diào)用getExit()改一改就行了。
    而此時(shí)我們發(fā)現(xiàn)其他部分不需要改,這就是松耦合的好處啊!

    框架+數(shù)據(jù)

    從程序中識(shí)別出框架和數(shù)據(jù),以代碼實(shí)現(xiàn)框架,將部分功能以數(shù)據(jù)的方式加載,這樣能在很大程度上實(shí)現(xiàn)可擴(kuò)展性。

    這個(gè)框架不是我們說的“Spring”、"MyBatis"那種。我們不想“if-else-”泛濫,就可以使用Handler,再使用Map來保存命令和Handler之間的關(guān)系,進(jìn)而破除“if-else-”硬編碼。

    我們使用Map是一個(gè)很秀的想法,但是函數(shù)不是對(duì)象,而Map的value必須是對(duì)象,所以我們才用的Handler。

    Handler被定義為一個(gè)類,這樣會(huì)很好:

    public class Handler {public void doCmd(String message){//TODO something} }

    而Game需要一個(gè)Map:

    private Map<String, Handler> handlers = new HashMap<>();

    那么在初始化Game的時(shí)候,在構(gòu)造器中直接使用put()初始化必要的命令:

    public Game() {handlers.put("go", new HandlerGo());handlers.put("help", new HandlerHelp());handlers.put("bye", new HandlerBye());createRooms(); }

    還需要一個(gè)play()方法,把main()的死循環(huán)扔進(jìn)去:

    public void play() {while (true) {String line = in.nextLine();String[] words = line.split(" ");if (words[0].equals("help")) {game.printHelp();} else if (words[0].equals("go")) {game.goRoom(words[1]);} else if (words[0].equals("bye")) {break;}} }

    這個(gè)方法需要改一改:

    public void play() {while (true) {String line = in.nextLine();String[] words = line.split(" ");Handler handler = handlers.get(words[0]);if (handler != null) {handler.doCmd(words[1]);}} }

    這個(gè)沒改好,因?yàn)闆]考慮退出的問題,但你要是考慮退出的問題,就需要if特判,就又繞回去了,所以需要再考慮:

    if (handler != null) {handler.doCmd(words[1]);if (handler.isBye()) {break;} }

    對(duì)應(yīng)的,Handler也要完善一下:

    public class Handler {protected Game game;public Handler(Game game) {this.game = game;}public void doCmd(String message){}public boolean isBye() {return false;} }

    之前我們也發(fā)現(xiàn)了HandlerGo、HandlerHelp、HandlerBye還沒出現(xiàn),自然是都要extends類Handler,把板子做出來:

    public class HandlerGo extends Handler {public HandlerGo(Game game) {super(game);}@Overridepublic void doCmd(String message) {game.goRoom(message);} } public class HandlerHelp extends Handler {public HandlerHelp(Game game) {super(game);}@Overridepublic void doCmd(String message) {System.out.print("迷路了嗎?你可以做的命令有:go bye help");System.out.println("如:\tgo east");} } public class HandlerBye extends Handler {public HandlerBye(Game game) {super(game);}@Overridepublic boolean isBye() {return true;} }

    而goRoom()要改成public:

    public void goRoom(String direction) {Room nextRoom = currentRoom.getExit(direction);if (nextRoom == null) {System.out.println("那里沒有門!");} else {currentRoom = nextRoom;showPrompt();} }

    再就是,構(gòu)造Game對(duì)象的時(shí)候要傳this:

    public Game() {handlers.put("go", new HandlerGo(this));handlers.put("help", new HandlerHelp(this));handlers.put("bye", new HandlerBye(this));createRooms(); }

    這樣就完成了基本的修改,只需微調(diào)即可完成系統(tǒng)修改。

    這里直接使用了普通類來表示Handler,其實(shí)也可以考慮接口與抽象類,這里點(diǎn)到為止。

    匿名內(nèi)部類讓代碼更優(yōu)雅

    在評(píng)論區(qū)看到下面的代碼(僅限于Game類的構(gòu)造器),寫的很不錯(cuò),還做了擴(kuò)展:

    public Game() {// 匿名類handlers.put("go", new Handler() {@Overridepublic void doCmd(String word) {goRoom(word);}});handlers.put("bye", new Handler() {@Overridepublic boolean isBye() {return true;}});handlers.put("help", new Handler() {@Overridepublic void doCmd(String word) {System.out.print("迷路了嗎?你可以做的命令有:");System.out.print(getHandlers());System.out.println(".");System.out.println("如: go east");}});handlers.put("gorandom", new Handler() {@Overridepublic void doCmd(String word) {goRandom();}});rooms = createRooms(); }

    是不是更秀了呢?哈哈,根本不再需要每一個(gè)具體的Handler類,也不需要this傳參,Nice!

    總結(jié)

    本文總結(jié)了一下如何修改給出的Castle代碼,使之基本做到高內(nèi)聚、低耦合和具備可擴(kuò)展性,也說明了很多編程的注意事項(xiàng)。
    原版代碼和課程講評(píng)來自浙江大學(xué)翁凱老師,感興趣的讀者可以去查看相關(guān)的資源!

    總結(jié)

    以上是生活随笔為你收集整理的【Java】《面向对象程序设计——Java语言》Castle代码修改整理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。