相信大家和我一样,在实际开发应用过程中,很难看代码中有直接new对象的情况,在一开始,也会被前辈告诫,尽量不要使用new来构造对象,尽量使用工厂方法获取对象云云。但是,我相信很多人和我一样,一开始是一知半解的状态,凭什么不能这样做,用工厂方法获取对象,多此一举吧?但是随着开发经验的逐渐累积,我发现将构造对象的逻辑暴露给客户端是十分不合适的设计,一方面你构造该对象的类中,可能并没有该类构造器需要的参数,这样就需要你单独创建这些参数;另一方面,构建对象的逻辑在很多类中使用,势必造成大量重复代码;最让人难受的是,如果你创建的对象的构造方法发生变更,那么,你就需要修改所有使用构建逻辑的类了,不便于维护,而且直接new对象,无法实现对象的动态创建。综合来说,我需要解决如下几个问题:
- 如何将创建某对象的代码与业务逻辑相分离
- 如何通过一个统一的接口创建对象
- 如何封装创建对象的逻辑
工厂模式就为了解决这种创建对象行为而总结出来的设计模式。
1.简单工厂模式
1.1 介绍
顾名思义,简单工厂模式实现起来非常简单明了,虽然不是GoF提到的23种模式之一,但是简单工厂模式是非常有用的模式。假设在编程中,需要创建不同的图形,采用简单工厂模式做如下结构设计:
定义了一个抽象父类Shape, 三个子类继承于父类Shape, 现在想要使用ShapeFactory来获取想要的图形。
1.2 简单代码实现
定义了Shape抽象类以及其派生类:
abstract class Shape { public String toString(){ return getClass().getSimpleName(); } } class Circle extends Shape { } class Triangle extends Shape { } class Rectangle extends Shape { }
定义简单工厂类,提供静态方法createShape(String type)返回对象引用, 该方法内有多个条件语句分支来判断用户需要创建哪个对象。
class ShapeFactory { public static Shape createShape(String type) { if ("circle".equals(type)) { return new Circle(); } else if ("triangle".equals(type)) { return new Triangle(); } else if ("rectangle".equals(type)) { return new Rectangle(); } else { return null; } } }
客户端代码
public class SimpleFactoryDemo1 { public static void main(String[] args) { Shape shape = ShapeFactory.createShape("circle"); System.out.println(shape); } }
可以看到,由于判断对象类型的逻辑放到了工厂中,用户不再需要知道创建对象的具体构造方法了,以后修改就只需要更改工厂即可。但是,对于这种实现方式,有个问题,就是在createShape方法中,有多个条件判断分支,如果新增一个Shape的子类,就需要增加一个分支,这中做法,违反了所谓开放-封闭原则,即对扩展开放,对修改关闭。
一般,遇见有较多条件分支语句的代码,都应该想一想是不是有办法来解决,反射就是一个好办法,反射能根据Class对象,动态地创建出其所“描绘”的对象,比如通过Circle.class的Class对象创建出Circle对象来。这里面,我们把工厂类修改为如下形式:
class ShapeFactory2 { /** 工厂对象,采用单例模式 */ private static ShapeFactory2 factory = new ShapeFactory2(); private ShapeFactory2(){} public static ShapeFactory2 instance() { return factory; } private Map<String, Class<? extends Shape>> classMap = new HashMap<>(); /** 注册对象信息 */ public void register(String id, Class<? extends Shape> shape) { classMap.put(id, shape); } /** * 获取对象 */ public Shape createShape(String id) throws Exception { Class<? extends Shape> shapeClass = classMap.get(id); Shape shape = shapeClass.newInstance(); return shape; } }
引入反射机制后,createShape()方法中,就不再有条件分支结构的判断了。相应的,需要首先注册该工厂所需的对象信息,注册方式有很多种,可以在项目启动时合适的地方设置静态代码块进行批量注册,也可以在每一个具体图形class加载的时候进行注册,本例子采用后者, 对图形类稍作修改:
class Circle extends Shape { static { ShapeFactory2.instance().register("circle", Circle.class); } } class Triangle extends Shape { static { ShapeFactory2.instance().register("triangle", Triangle.class); } } class Rectangle extends Shape { static { ShapeFactory2.instance().register("rectangle", Rectangle.class); } }
这时,需要保证图形class对象需要提前被加载, 我在客户端调用前,在静态代码块中将class加载了。
public class SimpleFactoryDemo1 { static { try { Class.forName("blog.design.factory.Circle"); Class.forName("blog.design.factory.Triangle"); Class.forName("blog.design.factory.Rectangle"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static void main(String[] args) throws Exception { Shape shape = ShapeFactory2.instance().createShape("circle"); System.out.println(shape); shape = ShapeFactory2.instance().createShape("rectangle"); System.out.println(shape); } }
反射机制引入简单工厂模式后,就避免了大量的条件分支语句,遵守了开放-封闭原则,而且采用单例模式创建工厂对象,保证系统中只有一套classMap, 便于注册和消除某图形对象。
2.工厂方法模式
2.1介绍
对于简单工厂模式加反射机制,避免了大量条件语句的判断,但是如果对于某个Shape比如Circle的构造器修改了,那么还是需要去修改工厂的代码加入条件分支判断,工厂方法模式主要为了:
- 为不同对象提供各自的工厂,从而有针对性的实现构造逻辑
- 提供统一的构造接口,把具体构造实现推迟到子类工厂去完成
工厂方法模式(Factory Method Pattern),定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法模式使一个类的实例化延迟到子类。 ---- 《大话设计模式》
结构图:
2.2代码实现
改造Shape子类,注意Circle的构造方法与其它两个子类不同
abstract class Shape { public String toString(){ return getClass().getSimpleName(); } } class Circle extends Shape { private double radius; public Circle(double radius) { this.radius = radius; } } class Triangle extends Shape { } class Rectangle extends Shape { }
定义工厂接口和工厂方法,并实现之, 不同工厂实现创建不同对象
interface IFactory { Shape createShape(); } class CircleFactory implements IFactory { private static Random rand = new Random(47); @Override public Shape createShape() { double radius = rand.nextDouble() * 10; return new Circle(radius); } } class RectangleFactory implements IFactory { @Override public Shape createShape() { return new Rectangle(); } } class TriangleFactory implements IFactory { @Override public Shape createShape() { return new Triangle(); } }
客户端调用
public class FactoryMethodDemo { public static void main(String[] args) { IFactory cFactory = new CircleFactory(); Shape circle = cFactory.createShape(); System.out.println(circle); IFactory rFactory = new RectangleFactory(); Shape rectangle = rFactory.createShape(); System.out.println(rectangle); IFactory tFactory = new TriangleFactory(); Shape triangle = tFactory.createShape(); System.out.println(triangle); } }
输出
Circle
Rectangle
Triangle
工厂方法的另一种应用场景时测试,测试时你也许会需要测试各种对象,所以你需要获取对象,而且与泛型机制相结合后,你就可以实现不同的工厂,创建不同类型的对象。以下是创建Shape的例子,如果你要创建Food,只要再定义一个FoodGenerator implements Generator<Food> 即可。
interface Generator<T> { T next(); } class ShapeGenerator implements Generator<Shape> { private Class[] types = {Circle.class, Rectangle.class, Triangle.class}; private static Random rand = new Random(47); @Override public Shape next() { Class<?> clazz = types[rand.nextInt(types.length)]; try { return (Shape) clazz.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } } public class FactoryMethodDemo { public static void main(String[] args) { Generator<Shape> generator = new ShapeGenerator(); Shape shape = generator.next(); } }
工厂方法模式使代码进一步解耦,因为讲实现逻辑推迟到了子类工厂,所以能针对不同的目标类使用不同的实现逻辑。但是工厂方法模式也有局限:
- 获取每个对象前,都需要确保相应工厂已经实例化了
- 如果增加新的想要创建的类,就需要增加新的工厂实现,可能导致项目中的类的数量剧烈增长
- 因为工厂类太多了,导致维护和Debug的时候不方便
3.抽象工厂模式
3.1 介绍
最初接触到工厂方法模式和抽象工厂模式的时候,总是不容易区分其到底有什么区别,按照我的理解,可以总结为以下两条:
- 工厂方法模式通常是通过一个工厂方法,创建同一类对象;抽象工厂模式是通过一个接口中不同的工厂方法,创建不同的对象,而这些对象之间通常有一定的依赖关系或关联关系。(当然抽象工厂方法也可以只创建一类对象,不过即便如此,也不能说抽象工厂模式就变成了工厂方法模式,因为它们的侧重点不同,所扩展的方向也就不同);
- 抽象工厂模式,总是应用在同一主题不同平台、版本等的对象获取,工厂方法模式通常是获取相同平台、版本下的同一类对象。
抽象工厂方法(Abstract Factory Pattern),提供一个创建一系列相关或者相互依赖对象的接口,而无需指定它们具体的类。
结构图如下:
3.2代码实现
假设在程序中,需要“画笔”和“画板”对象,而我们的app是支持windows和linux的,两者的实现不同,这样就需要一个抽象工厂去获取画笔和画板。
画笔、画板抽象类
abstract class Pan{ public String toString(){ return getClass().getSimpleName(); } } abstract class Panel { public String toString(){ return getClass().getSimpleName(); } }
windows和linux平台下的画笔、画板
class WindowsPan extends Pan { public void paint(){ System.out.println(this + " is painting!"); } } class LinuxPan extends Pan { public void paint(){ System.out.println(this + " is painting!"); } } class WindowsPanel extends Panel { } class LinuxPanel extends Panel { }
抽象工厂类
abstract class PaintingFactory { abstract Pan createPan(); abstract Panel createPanel(); }
windows和linux 下的具体工厂,生产相应环境下的画笔和画版
class WindowsPaintingFactory extends PaintingFactory{ @Override Pan createPan() { return new WindowsPan(); } @Override Panel createPanel() { return new WindowsPanel(); } } class LinuxPaintingFactory extends PaintingFactory { @Override Pan createPan() { return new LinuxPan(); } @Override Panel createPanel() { return new LinuxPanel(); } }
客户端调用,假设当前环境是windows系统
public class AbstractFactoryDemo { //假设当前环境为windows操作系统 public static final String OPERATING_SYSTEM = "windows"; static PaintingFactory getFactory(){ if (OPERATING_SYSTEM.equals("windows")) { return new WindowsPaintingFactory(); } else if (OPERATING_SYSTEM.equals("linux")) { return new LinuxPaintingFactory(); } return new WindowsPaintingFactory(); } public static void main(String[] args) { //获取当前环境下的工厂 PaintingFactory factory = getFactory(); Pan pan = factory.createPan(); Panel panel = factory.createPanel(); pan.paint(); System.out.println(panel); } }
输出结果
WindowsPan is painting!
WindowsPanel
抽象工厂模式还经常应用于不同数据库管理系统的数据源对象的创建过程。与工厂方法模式类似,当扩展的类过多时,将难以Debug。