工廠設計模式究竟怎么寫更優雅?!


閑來無事看了菜鳥教程的設計模式。看到了一個很有趣的討論,該討論是關於工廠設計模式的書寫形式。下面先看一下給出的基礎寫法,然后再看一下各位網友的優化。

工廠設計模式初衷:我們在創建對象時不會對客戶端暴露創建邏輯,並且是通過使用一個共同的接口來指向新創建的對象。即只需要告訴接口想要獲取對象的類型,然后接口就會創建好該類型對應的對象,並返回。

類圖如:

 

根據上面的類圖,可以給出如下實現:

1.首先創建shape.java接口

public interface Shape {
   void draw();
}

 2.創建接口的三個實現類:

public class Rectangle implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Inside Rectangle::draw() method.");
   }
}

 

public class Square implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Inside Square::draw() method.");
   }
}

 

public class Circle implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Inside Circle::draw() method.");
   }
}

 3.創建工廠:

public class ShapeFactory {
    
   //使用 getShape 方法獲取形狀類型的對象
   public Shape getShape(String shapeType){
      if(shapeType == null){
         return null;
      }        
      if(shapeType.equalsIgnoreCase("CIRCLE")){
         return new Circle();
      } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
         return new Rectangle();
      } else if(shapeType.equalsIgnoreCase("SQUARE")){
         return new Square();
      }
      return null;
   }
}

 4.使用該工廠,根據傳過來的類型信息獲取實體類的對象

public class FactoryPatternDemo {
 
   public static void main(String[] args) {
      ShapeFactory shapeFactory = new ShapeFactory();
 
      //獲取 Circle 的對象,並調用它的 draw 方法
      Shape shape1 = shapeFactory.getShape("CIRCLE");
 
      //調用 Circle 的 draw 方法
      shape1.draw();
 
      //獲取 Rectangle 的對象,並調用它的 draw 方法
      Shape shape2 = shapeFactory.getShape("RECTANGLE");
 
      //調用 Rectangle 的 draw 方法
      shape2.draw();
 
      //獲取 Square 的對象,並調用它的 draw 方法
      Shape shape3 = shapeFactory.getShape("SQUARE");
 
      //調用 Square 的 draw 方法
      shape3.draw();
   }
}

 5.輸出結果

Inside Circle::draw() method.
Inside Rectangle::draw() method.
Inside Square::draw() method.

 工廠模式的優缺點:

優點:一個調用者想創建一個對象,只要知道其名稱就可以了。 2、擴展性高,如果想增加一個產品,只要擴展一個工廠類就可以。 3、屏蔽產品的具體實現,調用者只關心產品的接口。

缺點:每次增加一個產品時,都需要增加一個具體類和對象實現工廠,使得系統中類的個數成倍增加,在一定程度上增加了系統的復雜度,同時也增加了系統具體類的依賴。

 

網友優化:

網友A給出的優化方案:

  使用反射機制可以解決每次增加一個產品時,都需要增加一個對象實現工廠的缺點。

  

public class ShapeFactory {
    public static Object getClass(Class<?extends Shape> clazz) {
        Object obj = null;

        try {
            obj = Class.forName(clazz.getName()).newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        return obj;
    }
}

 生產對象的時候使用強制轉換

Rectangle rect = (Rectangle) ShapeFactory.getClass(Rectangle.class);
rect.draw();
Square square = (Square) ShapeFactory.getClass(Square.class);
square.draw();

 這樣只需要一個對象實現工廠,不需要每次增加類型時都需要重新寫一個工廠的實現。

網友B對A又進行了進一步優化:

 

public class ShapeFactory {
    public static <T> T getClass(Class<? extends T> clazz) {
        T obj = null;

        try {
            obj = (T) Class.forName(clazz.getName()).newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        return obj;
    }
}

 使用泛型,去掉了每次獲取對象時的強制轉換

Rectangle rect = ShapeFactory.getClass(Rectangle.class);
rect.draw();

Shape square = ShapeFactory.getClass(Square.class);
square.draw();

 網友C又在B的基礎上進一步優化:

針對多個接口實現一個公共的工廠類

public class ObjectFactory {
    public <T> Object getObject(Class<T> clazz) {
       if (clazz == null ) {
           return null;
    }    
        Object obj  = null;
        try {
            obj = Class.forName(clazz.getName()).newInstance();
        } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return obj;
    }
}

 網友D又在C的基礎上進行了一次優化:

public class ShapeFactory {
    
   //使用 getShape 方法獲取形狀類型的對象
  public Shape getShape(Class<?> clazz){
        try {
            return (IShape) clazz.getConstructor().newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
        return null;
    }
}

 接下來有位E網友對以上ABCD網友的寫法給出了自己的見解:

  

  其實使用反射是一種不錯的辦法,但反射也是從類名反射而不能從類反射!

  先看一下工廠模式是用來干什么的——屬於創建模式,解決子類創建問題的。換句話來說,調用者並不知道運行時真正的類名,只知道從“Circle"可以創建出一個shape接口的類,至於類的名稱是否叫'Circle",調用者並不知情。所以真正的對工廠進行擴展的方式(防止程序員調用出錯)可以考慮使用一個枚舉類(防止傳入參數時,把circle拼寫錯誤)。

   如果調用者參肯定類型是Circle的話,那么其工廠沒有存在的意義了!

  比如 IShape shape = new Circle();這樣不是更好?也就是說調用者有了Circle這個知識是可以直接調用的,根據DP(迪米特法則)其實調用者並不知道有一個Circle類的存在,他只需要知道這個IShape接口可以計算圓面積,而不需要知道;圓這個類到底是什么類名——他只知道給定一個”circle"字符串的參數,IShape接口可以自動計算圓的面積就可以了!

   其實在.net類庫中存在這個模式的的一個典型的。但他引入的另一個概念“可插入編程協議”。

那個就是WebRequest req = WebRequest.Create("http://ccc......");可以自動創建一個HttpWebRequest的對象,當然,如果你給定的是一個ftp地址,他會自動創建一個FtpWebRequest對象。工廠模式中着重介紹的是這種通過某個特定的參數,讓你一個接口去干對應不同的事而已!而不是調用者知道了類!

  比如如果圓的那個類名叫"CircleShape“呢?不管是反射還是泛型都干擾了你們具體類的生成!其實這個要說明的問題就是這個,調用者(clinet)只知道IShape的存在,在創建時給IShape一個參數"Circle",它可以計算圓的面積之類的工作,但是為什么會執行這些工作,根據迪米特法則,client是不用知道的。

   我想問一下那些寫筆記的哥們,如果你們知道了泛型,那么為什么不直接使用呢?干嗎還需要經過工廠這個類呢?不覺得多余了嗎?

   如果,我只是說如果,如果所有從IShape繼承的類都是Internal類型的呢?而client肯定不會與IShape一個空間!這時,你會了現你根本無法拿到這個類名!

  Create時使用注冊機制是一種簡單的辦法,比如使用一個枚舉類,把功能總結到一處。而反射也是一種最簡單的辦法,調用者輸入的名稱恰是類名稱或某種規則時使用,比如調用者輸入的是Circle,而類恰是CircleShape,那么可以通過輸入+”Shape"字符串形成新的類名,然后從字符串將運行類反射出來!

   工廠的創建行為,就這些作用,還被你們用反射或泛型轉嫁給了調用者(clinet),那么,這種情況下,要工廠類何用?!

 

自認為E的見解是從工廠設計模式的根本出發的,大致可以總結出如下實現:

public enum Factory {
    CIRCLE(new Circle(),"CIRCLE"),
    RECTANGLE(new Rectangle(),"RECTANGLE"),
    SQUARE(new Square(),"SQUARE");
    
    // 成員變量  
    private Shape shape;  
    private String name;  
    
    // 普通方法  
    public static Shape getShape(String name) {  
        for (Factory c : Factory.values()) {  
            if (c.name == name) {  
                return c.shape;  
            }  
        }  
        return null;  
    } 
    // 構造方法  
    private Factory(Shape shape, String name) {  
        this.shape = shape;  
        this.name = name;  
    } 
    public String getName() {
        return name;
    }
    public Shape getShape() {
        return shape;
    }


    public void setShape(Shape shape) {
        this.shape = shape;
    }


    public void setName(String name) {
        this.name = name;
    } 
    
}

 可使用枚舉根據類型來創建想要的對象:

Factory.getShape("CIRCLE").draw();
Factory.getShape("RECTANGLE").draw();
Factory.getShape("SQUARE").draw();

 


免責聲明!

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



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