讲解常见的工厂模式_使用工厂模式的好处

很多人都会纠结于“既然都有了构造函数,何必再折腾那么多事情呢” 。为了解答这个问题,先解释下构造函数是干什么用的 。
先用最早出现的C,创建资源差不多要这么干:
some_struct * p = (some_struct*)malloc(sizeof(some_struct));init_some_struct(p);do_something(p);即先分配内存,再做类型转换,再初始化,然后使用 。而在OOP的时代,创建一个对象是很频繁的事情 。同时,一个没初始化的数据结构是无法使用的 。因此,构造函数被发明出来,将分配内存+初始化合并到了一起 。如C++的语法是:
SomeClz *p = new SomeClz();do_something(p); // orp.do_something_else();java也沿用了这个设计 。
但是,整个构造函数完成的工作从更高层的代码设计角度还是太过于初级 。因此复杂的创建逻辑还是需要写代码来控制 。所以还是需要:
SomeClz * createSomeClz(...) { // 做一些逻辑 SomeClz *p = new SomeClz(); // 或者复用已经有的对象 // 再做一些额外的初始化 return p;} 这就是Factory的雏形 。
【讲解常见的工厂模式_使用工厂模式的好处】Factroy要解决的问题是:希望能够创建一个对象,但创建过程比较复杂,希望对外隐藏这些细节 。
请特别留意“创建过程比较复杂“这个条件 。如果不复杂,用构造函数就够了 。比如你想用一个HashMap时也要搞一个factory,这就很中2了 。
好,那什么是“复杂的创建过程呢“?举几个例子:
例子1: 创建对象可能是一个pool里的,不是每次都凭空创建一个新的 。而pool的大小等参数可以用另外的逻辑去控制 。比如连接池对象,线程池对象就是个很好的例子 。
例子2: 对象代码的作者希望隐藏对象真实的的类型,而构造函数一定要真实的类名才能用 。比如作者提供了
abstract class Foo {//...}而真实的实现类是
public class FooImplV1 extends Foo {// ...}但他不希望你知道FooImplV1的存在(没准下次就改成V2了),只希望你知道Foo,所以他必须提供某种类似于这样的方式让你用:
Foo foo = FooCreator.create();// do something with foo ...例子3: 对象创建时会有很多参数来决定如何创建出这个对象 。比如你有一个数据写在文件里,可能是xml也可能是json 。这个文件的数据可以变成一个对象,大概就可以搞成 。
Foo foo = FooCreator.fromFile("/path/to/the/data-file.ext");再比如这个文件是描述一个可以显示在浏览器的UI的基础数据 。而不同浏览器可以正确显示的需要的数据不太一样 。这个“不一样”可以表达为:
Foo foo = FooCreator.fromFile("/path/to/the/data-file.ext", BrowserType.CHROME);这里第二个参数"BrowserType"是一个枚举,表示如何去生成指定要求的对象 。所以这个fromFile内部可能是:
public Foo fromFile(String path, BrowserType type) {byte[] bytes = Files.load(path);switch (type) {case CHROME: return new FooChromeImpl(bytes);case IE8: return new FooIE8V1Impl(bytes);// ...}}当然,实际场景可能会复杂得多,会有大量的配置参数 。
Foo foo = FooCreator.fromFile("....", param1, param2, param3, ...);如果需要,可以帮params弄成一个Config对象 。而如果这个Config对象也很复杂,也许还得给Config弄个Factory 。如果Factory本身的创建也挺复杂呢?嗯,弄个Factory的Factory 。
例子4:简化一些常规的创建过程 。上面可以看到根据配置去创建一个对象也很复杂 。但可能95%的情况我们就创建某个特定类型的对象 。这时可以弄个函数直接省略那些配置过程 。纯粹就是为了方便 。
Foo foo = FooCreator.chromeFromFile("/path/to/the/date-file.ext");现实当中,比如Java的线程池的相关创建api(如
Executors.newFixedThreadPool等)就是这么干的 。
例子5: 创建一个对象有复杂的依赖关系,比如Foo对象的创建依赖A,A又依赖B,B又依赖C…… 。于是创建过程是一组对象的的创建和注入 。手写太麻烦了 。所以要把创建过程本身做很好地维护 。对,Spring IoC就是这么干的 。
例子6: 你知道怎么创建一个对象,但是无法把控创建的时机 。你需要把“如何创建”的代码塞给“负责什么时候创建”的代码 。后者在适当的时机,就回调创建的函数 。
在支持用函数传参的语言,比如js,go等,直接塞创建函数就行了 。对于名词王国java,就得搞个XXXXFactory的类再去传 。Spring IoC 也利用了这个机制,可以了解下FactoryBean