工厂设计模式究竟怎么写更优雅?!

Posted blog411032

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了工厂设计模式究竟怎么写更优雅?!相关的知识,希望对你有一定的参考价值。

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

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

类图如:

 

技术分享图片

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

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();

 

以上是关于工厂设计模式究竟怎么写更优雅?!的主要内容,如果未能解决你的问题,请参考以下文章

怎么使用es6 的class 优雅地写出单例模式

2021突破瓶颈:巧用策略模式完美应付产品四次需求变更,代码如何优雅稳健成长

Django admin注册model究竟要怎么写才优雅 批量注册model

如何写更少的 if else

小型工厂企业网站究竟该怎么做好SEO优化,从而带来更多订单?

这个代码片段究竟做了啥?