對比總結三個工廠模式(簡單工廠,工廠方法,抽象工廠)


前言

簡單工廠模式,工廠方法模式,抽象工廠模式,這三個模式,當然還有單例模式,建造者模式等等,應該是日常工作中常用的,尤其是工廠模式,應該是最最常見的模式,對理解面向對象有重要的實際意義。

簡單工廠模式

最簡單,最直接,能滿足大部分日常需求,不足是工廠類太簡單——無法滿足開閉原則,對多個產品的擴展不利

工廠方法模式——交給子類去創建

工廠方法模式,有了進步,把工廠類進行改進,提升為一個抽象類(接口),把對具體產品的實現交給對應的具體的子類去做,解耦多個產品之間的業務邏輯。

前面都是針對一個產品族的設計,如果有多個產品族的話,就可以使用抽象工廠模式

抽象工廠模式

抽象工廠模式的工廠,不再維護一個產品等級的某個產品(或說一個產品結構的某個產品更好理解),而是維護產品結構里的所有產品(橫向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電子書,資料,幫忙內推,歡迎拍磚!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM