闲来无事看了菜鸟教程的设计模式。看到了一个很有趣的讨论,该讨论是关于工厂设计模式的书写形式。下面先看一下给出的基础写法,然后再看一下各位网友的优化。
工厂设计模式初衷:我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。即只需要告诉接口想要获取对象的类型,然后接口就会创建好该类型对应的对象,并返回。
类图如:
根据上面的类图,可以给出如下实现:
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 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 staticT getClass(Class 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 { publicObject getObject(Class 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("");可以自动创建一个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();