zoukankan      html  css  js  c++  java
  • 工厂设计模式究竟怎么写更优雅?!

    闲来无事看了菜鸟教程的设计模式。看到了一个很有趣的讨论,该讨论是关于工厂设计模式的书写形式。下面先看一下给出的基础写法,然后再看一下各位网友的优化。

    工厂设计模式初衷:我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。即只需要告诉接口想要获取对象的类型,然后接口就会创建好该类型对应的对象,并返回。

    类图如:

    根据上面的类图,可以给出如下实现:

    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();
    
  • 相关阅读:
    MSAA, UIA brief explanation
    《微软的软件测试之道》读书笔记 之 非功能测试
    《微软的软件测试之道》读书笔记 之 结构测试技术
    《软件测试方法和技术》 读书笔记
    Gumshoe
    ng-template
    script跨域之360搜索
    src与href的异同
    跨域
    js引入script
  • 原文地址:https://www.cnblogs.com/blog411032/p/9721599.html
Copyright © 2011-2022 走看看