按理說應該把書全都看完一遍,再開始寫博客比較科學,會有比較全面的認識。 但是既然都決定要按規律更新博客了,只能看完一個設計模式寫一篇了。 也算是逼自己思考了,不是看完就過,至少得把代碼自己都敲一遍。 剛開始可能寫的比較淺顯,更像是讀書筆記,只能未來回來完善了。 廢話啰嗦到這,開始正題。 文章是以一個 ...
按理說應該把書全都看完一遍,再開始寫博客比較科學,會有比較全面的認識。 但是既然都決定要按規律更新博客了,只能看完一個設計模式寫一篇了。 也算是逼自己思考了,不是看完就過,至少得把代碼自己都敲一遍。 剛開始可能寫的比較淺顯,更像是讀書筆記,只能未來回來完善了。 廢話啰嗦到這,開始正題。
文章是以一個面試中,面試官要求寫一個計算器開頭的。 巧的是,在之前的一次面試中,剛好面試官也問了這個問題,當時我並不能給出令人滿意的答案,只恨沒有早點學習設計模式啊。 代碼不光是要完成功能就完事了,還要考慮健壯性、復用性、可擴展性等等。 學習設計模式的意義也在於此,幫助我們寫出更加優雅的代碼。 那麼回到之前的問題,如果現在要你寫一個計算器的程式,你會怎麼寫呢? 我的第一反應和書中的菜鳥一樣,特別還是在面試中,時間緊迫,當然是把功能完成了就OK,為什麼要考慮那麼複雜,寫一個方法直接搞定: (這裡為了代碼的簡潔性,暫時不考慮用戶不按規範輸入,除零、浮點失精這種健壯性問題。)
1 package designpattern.staticfactorymethod; 2 import java.util.Scanner; 3 public class Calculator { 4 public static void main(String[] args) { 5 Scanner scanner = new Scanner(System.in); 6 System.out.println("請輸入一個數字"); 7 double num1 = scanner.nextDouble(); 8 System.out.println("請輸入一個運算符:+、-、*、/"); 9 String operator = scanner.next();// 不能用nextLine(),會把上一個回車給吸收 10 System.out.println("請輸入一個數字"); 11 double num2 = scanner.nextDouble(); 12 switch (operator) { 13 case "+": 14 System.out.println(num1 + num2); 15 break; 16 case "-": 17 System.out.println(num1 - num2); 18 break; 19 case "*": 20 System.out.println(num1 * num2); 21 break; 22 case "/": 23 System.out.println(num1 / num2); 24 break; 25 default: 26 break; 27 } 28 scanner.close(); 29 } 30 }
最多把計算的方法單拉出來:
package designpattern.staticfactorymethod; import java.util.Scanner; public class Calculator2 { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("請輸入一個數字"); double num1 = scanner.nextDouble(); System.out.println("請輸入一個運算符:+、-、*、/"); String operator = scanner.next();// 不能用nextLine(),會把上一個回車給吸收 System.out.println("請輸入一個數字"); double num2 = scanner.nextDouble(); switch (operator) { case "+": System.out.println(plus(num1, num2)); break; case "-": System.out.println(minus(num1, num2)); break; case "*": System.out.println(multiply(num1, num2)); break; case "/": System.out.println(divide(num1, num2)); break; default: break; } scanner.close(); } static double plus(double num1, double num2) { return num1 + num2; } static double minus(double num1, double num2) { return num1 - num2; } static double multiply(double num1, double num2) { return num1 * num2; } static double divide(double num1, double num2) { return num1 / num2; } }這樣雖然運算方法可以復用,但是顯示和運算放在一起,你說它是個工具類吧,也不是,顯得有些不倫不類。 要我來改,我最多把運算方法單拉成一個工具類,就結束了:
package designpattern.staticfactorymethod; public class CalculatorUtil { static double plus(double num1, double num2) { return num1 + num2; } static double minus(double num1, double num2) { return num1 - num2; } static double multiply(double num1, double num2) { return num1 * num2; } static double divide(double num1, double num2) { return num1 / num2; } }我發現自己用java寫了這麼長時間的代碼,好歹是個面向對象的語言,但是封裝用的很熟練,但是繼承和多態幾乎就沒有用過。 雖然《Thinking In Java》的作者說不能濫用繼承: 但是完全不用也是有問題的。 書中寫的例子對我蠻有啟發的。 假如我需要增加一個運算開根(sqrt)運算,怎麼辦? 只能去改那個工具類,但是這會帶來問題。 首先,你增加一個方法,要讓之前已經寫好的加減乘除方法一起參與編譯,增加了你不小心影響到之前方法的風險。 其次,可能之前的方法含有敏感信息,你並沒有許可權看(這個問題我之前還真的從來沒有思考過) 那應該怎麼辦? 自然是把所有的運算方法單獨放在一個類里,這樣就互相之間不影響,想新增直接新增類就可以了,不用看之前的代碼。 第一反應是寫成這樣,只是單純的寫成單獨的類:(為了方便顯示,寫成了內部類;同樣為了簡潔setter、getter我就不寫了)
package designpattern.staticfactorymethod; import java.util.Scanner; public class Calculator3 { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("請輸入一個數字"); double num1 = scanner.nextDouble(); System.out.println("請輸入一個運算符:+、-、*、/"); String operator = scanner.next();// 不能用nextLine(),會把上一個回車給吸收 System.out.println("請輸入一個數字"); double num2 = scanner.nextDouble(); Calculator3 calculator3 = new Calculator3(); switch (operator) { case "+": System.out.println(calculator3.new Plus(num1, num2).calculate()); break; case "-": System.out.println(calculator3.new Minus(num1, num2).calculate()); break; case "*": System.out.println(calculator3.new Multiply(num1, num2).calculate()); break; case "/": System.out.println(calculator3.new Divide(num1, num2).calculate()); break; default: break; } scanner.close(); } class Plus { double num1; double num2; Plus(double num1, double num2) { this.num1 = num1; this.num2 = num2; } double calculate() { return num1 + num2; } } class Minus { double num1; double num2; Minus(double num1, double num2) { this.num1 = num1; this.num2 = num2; } double calculate() { return num1 - num2; } } class Multiply { double num1; double num2; Multiply(double num1, double num2) { this.num1 = num1; this.num2 = num2; } double calculate() { return num1 * num2; } } class Divide { double num1; double num2; Divide(double num1, double num2) { this.num1 = num1; this.num2 = num2; } double calculate() { return num1 / num2; } } }你會發現這幾個類結構都是一模一樣的,我幾乎也是複製粘貼出來的,咱們是不是可以提取出一個共同的父類來?
package designpattern.staticfactorymethod; public abstract class Calculate { double num1; double num2; Calculate() { } Calculate(double num1, double num2) { this.num1 = num1; this.num2 = num2; } public abstract double calculate(); }
具體運運算元類們:
package designpattern.staticfactorymethod; public class Plus extends Calculate { Plus() { } Plus(double num1, double num2) { super(num1, num2); } @Override public double calculate() { return num1 + num2; } }
package designpattern.staticfactorymethod; public class Minus extends Calculate { Minus() { } Minus(double num1, double num2) { super(num1, num2); } @Override public double calculate() { return num1 - num2; } }
package designpattern.staticfactorymethod; public class Multiply extends Calculate { Multiply() { } Multiply(double num1, double num2) { super(num1, num2); } @Override public double calculate() { return num1 * num2; } }
package designpattern.staticfactorymethod; public class Divide extends Calculate { Divide() { } Divide(double num1, double num2) { super(num1, num2); } @Override public double calculate() { return num1 / num2; } }繼承終於派上用場了,你會發現這樣還是沒辦法使用父類,想要具體的運算還是得直接去實例化子類的對象,和提取父類之前沒有什麼區別。 接下來,終於,今天的主題要出場了,簡單工廠設計模式。 先上一個百度百科解釋: 簡單工廠模式是屬於創建型模式,又叫做靜態工廠方法(Static Factory Method)模式,但不屬於23種GOF設計模式之一。簡單工廠模式是由一個工廠對象決定創建出哪一種產品類的實例。簡單工廠模式是工廠模式家族中最簡單實用的模式,可以理解為是不同工廠模式的一個特殊實現。 簡單來說,它就是用來創建對象的,假設有那麼一家神秘的工廠,什麼都能生產,你只要對它說你想要什麼,它就會給你生產出來,你並不需要關心,工廠內部具體是怎麼生產的。像這樣:
當然實際代碼中,不能像上面這樣天馬行空,術業還是要有專攻的,比如一個工廠專門生產各種各樣的女朋友,額。。
比如iphon的工廠專門生產各種型號的ipone。
把這個思想用到我們的程式中,我們需要一個工廠,接受一個參數,然後它就能返回相應的對象:
package designpattern.staticfactorymethod; public class CalculateFactory { public static Calculate create(String operate) { switch (operate) { case "+": return new Plus(); case "-": return new Minus(); case "*": return new Multiply(); case "/": return new Divide(); } return null; } }前面的類都新增了一個預設的構造方法,就是為了這裡用,我也思考過,可不可以用統一的帶參數構造方法,但我看好像沒有人這麼寫,肯定有它的道理,這裡寫下自己粗淺的理解,歡迎交流、指正: 工廠的任務只是生產對象,加上參數就相當於和業務攪在一起,變得很不靈活,今天你只想初始化汽車的品牌,明天你又想初始化汽車的顏色,這樣,你汽車類、工廠類、調用工廠的類全要改,如果只是預設的構造方法的話,至少你的工廠類是不用改的。 接下來看調用的方法:
package designpattern.staticfactorymethod; import java.util.Scanner; public class Calculator4 { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("請輸入一個數字"); double num1 = scanner.nextDouble(); System.out.println("請輸入一個運算符:+、-、*、/"); String operator = scanner.next();// 不能用nextLine(),會把上一個回車給吸收 System.out.println("請輸入一個數字"); double num2 = scanner.nextDouble(); Calculate calculate = CalculateFactory.create(operator); calculate.num1 = num1; calculate.num2 = num2; System.out.println(calculate.calculate()); scanner.close(); } }
這回多態也出場了,通過多態,統一用父類接受創建的具體子類,當需要增加運算方式時,只需要修改工廠類和具體的運算類,調用的地方不用變,降低了模塊間的耦合性,提高了系統的靈活性。