前言
簡單工廠模式,工廠方法模式,抽象工廠模式,這三個模式,當然還有單例模式,建造者模式等等,應該是日常工作中常用的,尤其是工廠模式,應該是最最常見的模式,對理解面向對象有重要的實際意義。
簡單工廠模式
最簡單,最直接,能滿足大部分日常需求,不足是工廠類太簡單——無法滿足開閉原則,對多個產品的擴展不利
工廠方法模式——交給子類去創建
工廠方法模式,有了進步,把工廠類進行改進,提升為一個抽象類(接口),把對具體產品的實現交給對應的具體的子類去做,解耦多個產品之間的業務邏輯。
前面都是針對一個產品族的設計,如果有多個產品族的話,就可以使用抽象工廠模式
抽象工廠模式
抽象工廠模式的工廠,不再維護一個產品等級的某個產品(或說一個產品結構的某個產品更好理解),而是維護產品結構里的所有產品(橫向x軸),具體到代碼就是多個抽象方法去對應產品等級結構的各個產品實例
具體的工廠類實現抽象工廠接口,去對應各個產品族,每一個具體工廠對一個產品族,獲得該產品族的產品結構(所有產品)
抽象工廠模式中的方法對應產品等級結構(每個類型中的具體產品),具體子工廠對應不同的產品族(產品類型)
面試題——計算器
想到一個面試題: 寫一個簡單的計算器,滿足加減乘除運算。邏輯比較簡單:接受計算數據的輸入,進行計算,返回結果。用Java實現。
面向過程版
面向過程的設計本身沒有錯,但是如果面試的是 java 的相關職位,使用一門面向對象的語言這樣寫是非常危險的。因為這樣寫,誰都會,但凡學過編程的,沒有不會的。更重要的問題是,這樣寫的目的僅僅是為了完成任務,沒有任何面向對象的思維體現。實際業務中,類似的程序一旦擴展,這樣的代碼是沒有辦法維護的。
缺點:完全面向過程設計,所有邏輯都集中在一個類(方法、函數里),缺少代碼的重用……
這里的除 0 異常檢測也是考點之一。
public class Main { public static void main(String[] args) { // 1、先接受數據的輸入 // 2、進行計算 // 3、返回計算結果 System.out.println("******計算器********\n 請輸入第一個數:"); Scanner scanner = new Scanner(System.in); String num1 = scanner.nextLine(); System.out.println("請輸入運算符:"); String operation = scanner.nextLine(); System.out.println("請輸入第二個數:"); String num2 = scanner.nextLine(); System.out.println("開始計算。。。。。。"); double result = 0; if ("+".equals(operation)) { result = Double.parseDouble(num1) + Double.parseDouble(num2); } else if ("-".equals(operation)) { result = Double.parseDouble(num1) - Double.parseDouble(num2); } else if ("*".equals(operation)) { result = Double.parseDouble(num1) * Double.parseDouble(num2); } else if ("/".equals(operation)) { if (Double.parseDouble(num2) != 0) { result = Double.parseDouble(num1) / Double.parseDouble(num2); } else { System.out.println("除數不能為0!"); return; } } System.out.println(num1 + operation + num2 + " = " + result); } }
簡單面向對象版
面向對象的設計就是把各個操作抽象為一個個的類,加法類,減法類,乘法類,除法類……每個運算類的職責就是進行屬於自己的運算符的計算,客戶端去調用對應的運算類即可。
代碼如下:
public abstract class Operation { private double num1; private double num2; public double getNum1() { return num1; } public void setNum1(double num1) { this.num1 = num1; } public double getNum2() { return num2; } public void setNum2(double num2) { this.num2 = num2; } public abstract double getResult(); }
具體的運算符子類,只用加法舉例,其他省略。
public class Add extends Operation { @Override public double getResult() { return this.getNum1() + this.getNum2(); } }
客戶端代碼
// 1、計算數據的輸入 // 2、進行計算 // 3、返回計算結果 System.out.println("******計算器********\n 請輸入第一個數:"); Scanner scanner = new Scanner(System.in); String num1 = scanner.nextLine(); System.out.println("請輸入運算符:"); String operation = scanner.nextLine(); System.out.println("請輸入第二個數:"); String num2 = scanner.nextLine(); System.out.println("開始計算。。。。。。"); double result = 0; // 類型轉換 double a = Double.parseDouble(num1); double b = Double.parseDouble(num2); if ("+".equals(operation)) { Operation o = new Add(); o.setNum1(a); o.setNum2(b); result = o.getResult(); }
寫到這里,貌似比之前也沒什么大的改變,只是使用了面向對象的一丟丟,使用了類……在客戶端還是需要顯式的去 new 對應的運算類對象進行計算,客戶端里還是維護了大量的業務邏輯……
繼續改進,使用工廠模式——簡單工廠模式
簡單工廠模式版
一般學過的人,會立即想到該模式
public abstract class Operation { private double num1; private double num2; public double getNum1() { return num1; } public void setNum1(double num1) { this.num1 = num1; } public double getNum2() { return num2; } public void setNum2(double num2) { this.num2 = num2; } public abstract double getResult(); } public class Add extends Operation { @Override public double getResult() { return this.getNum1() + this.getNum2(); } } public class Sub extends Operation { @Override public double getResult() { return this.getNum1() - this.getNum2(); } } /////////////// 簡單工廠類(也可以使用反射機制) public class OpreationFactory { public static Operation getOperation(String operation) { if ("+".equals(operation)) { return new Add(); } else if ("-".equals(operation)) { return new Sub(); } return null; } } ////////////// 調用者(客戶端) public class Main { public static void main(String[] args) { System.out.println("******計算器********\n請輸入第一個數:"); Scanner scanner = new Scanner(System.in); String num1 = scanner.nextLine(); System.out.println("請輸入運算符:"); String operation = scanner.nextLine(); System.out.println("請輸入第二個數:"); String num2 = scanner.nextLine(); System.out.println("開始計算。。。。。。"); double result = 0; // 類型轉換 double a = Double.parseDouble(num1); double b = Double.parseDouble(num2); Operation oper = OpreationFactory.getOperation(operation); // TODO 有空指針異常隱患 oper.setNum1(a); oper.setNum2(b); result = oper.getResult(); System.out.println(num1 + operation + num2 + " = " + result); } }
這樣寫,客戶端(調用者)無需反復修改程序,也不需要關注底層實現,調用者只需要簡單了解或者指定一個工廠的接口,然后去調用即可一勞永逸,而底層的修改不會影響調用者的代碼結構——實現了解耦。且每個操作符類都各司其職,單一職責,看着還可以
但是這時候面試官說了,給我增加開平方運算,回想之前的簡單工廠設計模式,每次增加新的產品都需要去修改原來的工廠代碼——這樣不符合OCP,那么自然想到了工廠方法模式
工廠方法模式版
只舉個加法的例子得了
// 將工廠,又抽象了一層 public interface OpreationFactory { Operation getOperation(); } ////// 具體工廠類,生產不同的產品,比如加減乘除,開平方等 public class AddFactory implements OpreationFactory { @Override public Operation getOperation() { return new Add(); } } ///// 抽象的產品實體類 public abstract class Operation { private double num1; private double num2; public double getNum1() { return num1; } public void setNum1(double num1) { this.num1 = num1; } public double getNum2() { return num2; } public void setNum2(double num2) { this.num2 = num2; } public abstract double getResult(); } public class Add extends Operation { @Override public double getResult() { return this.getNum1() + this.getNum2(); } } /////////// 客戶端 public class Main { public static void main(String[] args) { System.out.println("******計算器********\n 請輸入第一個數:"); Scanner scanner = new Scanner(System.in); String num1 = scanner.nextLine(); System.out.println("請輸入運算符:"); String operation = scanner.nextLine(); System.out.println("請輸入第二個數:"); String num2 = scanner.nextLine(); System.out.println("開始計算。。。。。。"); double result = 0; // 類型轉換 double a = Double.parseDouble(num1); double b = Double.parseDouble(num2); // TODO 這里又需要判斷了 if ("+".equals(operation)) { // 得到加法工廠 OpreationFactory opreationFactory = new AddFactory(); // 計算 + Operation oper = opreationFactory.getOperation(); oper.setNum1(a); oper.setNum2(b); result = oper.getResult(); } // ...... System.out.println(num1 + operation + num2 + " = " + result); } }
工廠方法模式雖然避免了每次擴展運算符的時候,都修改工廠類,但是把判斷的業務邏輯放到了客戶端里,各有缺點吧……不要為了面向對象而面向對象。
改進的工廠方法模式版
不過,還是可以改進的……使用反射動態加載類 + 配置文件/注解 等等,且對重復代碼進行提煉和封裝……其實框架就這么一步步來的。
代碼如下(+和-):
public interface OpreationFactory { Operation getOperation(); } public class AddFactory implements OpreationFactory { @Override public Operation getOperation() { return new Add(); } } public class SubFactory implements OpreationFactory { @Override public Operation getOperation() { return new Sub(); } } public abstract class Operation { private double num1; private double num2; public double getNum1() { return num1; } public void setNum1(double num1) { this.num1 = num1; } public double getNum2() { return num2; } public void setNum2(double num2) { this.num2 = num2; } public abstract double getResult(); } public class Add extends Operation { @Override public double getResult() { return this.getNum1() + this.getNum2(); } } public class Sub extends Operation { @Override public double getResult() { return this.getNum1() - this.getNum2(); } } ////////////// 封裝了一些操作 public enum Util { MAP { // TODO 寫在配置文件里 @Override public Map<String, String> getMap() { Map<String, String> hashMap = new HashMap<>(); hashMap.put("+", "compute.object.AddFactory"); hashMap.put("-", "compute.object.SubFactory"); return hashMap; } public double compute(double a, double b, OpreationFactory opreationFactory) { Operation oper = opreationFactory.getOperation(); oper.setNum1(a); oper.setNum2(b); return oper.getResult(); } }; public abstract Map<String, String> getMap(); public abstract double compute(double a, double b, OpreationFactory opreationFactory); } //////////// 客戶端 public class Main { private static double result = 0; private static double a; private static double b; public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { System.out.println("******計算器********\n 請輸入第一個數:"); Scanner scanner = new Scanner(System.in); String num1 = scanner.nextLine(); System.out.println("請輸入運算符:"); String operation = scanner.nextLine(); System.out.println("請輸入第二個數:"); String num2 = scanner.nextLine(); System.out.println("開始計算。。。。。。"); a = Double.parseDouble(num1); b = Double.parseDouble(num2); Class clazz = Class.forName(MAP.getMap().get(operation)); result = MAP.compute(a, b, (OpreationFactory) clazz.newInstance()); System.out.println(num1 + operation + num2 + " = " + result); } }
到這里也差不多了,雖然還有很多問題……關鍵是思想的掌握
歡迎關注
dashuai的博客是終身學習踐行者,大廠程序員,且專注於工作經驗、學習筆記的分享和日常吐槽,包括但不限於互聯網行業,附帶分享一些PDF電子書,資料,幫忙內推,歡迎拍磚!