0 创建型模式
工厂模式说起来很虚的感觉,如果构造函数很简单, 直接就可以new
出来。那还需要工厂模式做什么?
设计模式嘛,就是要将简单的东西标准化,用统一的模式或者方式去做某件事情,包括创建对象。更重要的是设计模式一直在强调解耦。怎么解耦?通常的方法就是中间加一层——抽象层,高层抽象,底层抽象都向这个中间层靠。
那换句话说,其实是在“封装”代码。将一些常用,会用到的代码进一步封装。
因为啊,以后底层的类可能要更改,底层类的构造函数可能要更改,如果使用了工厂模式,那么只需要改这一个抽象层就行了,不用在所有代码里挨个找。
这种方式符合迪米特法则。
1 简单工厂模式
简单工厂模式不是23中设计模式中的一种。
1.0 需求
在继承中,一个基类有多个派生类,在不同的场景中可能需要不同派生类的对象。
而且派生类的构造方式可能改变,或许可能某个派生类被删除,使用new的方法创建的方法变得不可靠。
因此使用一种同一的方法来创建对象。
更深层的原因,可能是所需要的对象的代码,不属于我们维护。因此,底层的代码提供了同一的接口创建对象。
这就符合最少知道原则,使用了一个隔离层,来与实际功能类进行操作。
1.1 实现
1个基类,1个创建类,多个派生类。基类作为抽象层,为了让创建类能够返回派生类,符合依赖倒置原则。
创建类只有重载了的静态成员函数,返回值为基类指针。重载的原因在于,可能不同的派生类具有不同的参数。
//基类 class AB_Compiler { public: virtual void do() = 0; }; //派生类1 class Gnu : public AB_Compiler { public: virtual void do(); }; //派生类1 class Clang: public AB_Compiler { public: virtual void do(); }; //创建类 class Compiler { public: // 可能的多个重载 // create 内部也可能是有多个 if 分支 static AB_Compiler* create(/*可能不同的参数*/); static AB_Compiler* create(/*可能不同的参数*/); }
1.2 缺点
当新的派生类产生的时候,创建类内部的代码需要变更。因此不符合开闭原则。
2 工厂模式
2.0 需求
为了解决简单工厂不符合开闭原则,因此出现了工厂模式。
工厂模式,则是在简单工厂模式的基础上再加一层抽象类。
2.1 实现
1个实际功能类的基类,1个创建类的基类,多个实际功能类,对应的多个实际功能类的创建类(继承自创建类的基类)。
使用的时候,先创建一个创建类,然后调用方法,创建对应的对象
//基类 class AB_Compiler { public: virtual void do() = 0; }; //派生类1 class Gnu : public AB_Compiler { public: virtual void do(); }; //派生类1 class Clang: public AB_Compiler { public: virtual void do(); }; //创建类基类 class Creator { public: virtual AB_Compiler* create(/*参数*/)=0; }; // 对应的创建类 class GunCreator :public Creator { public: virtual AB_Compiler* create(/*参数*/); }; class ClangCreator :public Creator { public: virtual AB_Compiler* create(/*参数*/); }; int main() { Creator* creator = new ClangCreator; AB_Compiler* clang = creator->create(); }
代码又臭又长,折叠了。
当有新的实际功能类的时候,就添加对应的创建类。
2.2 缺点
工厂模式其实并没有提供统一的接口,因为在实例化创建类的时候,仍然需要知道对应创建类的类名。
因此这个就是简单一个迪米特法则,使用了一个隔离层(也就是创建类)
但实际上,创建类并不需要继承,直接创建对应的创建类对象,使用静态函数的方式即可。因为最终我们都需要知道对应的创建类的类型。
工厂模式是符合开闭原则的。在扩展的时候只增加类,而不需要修改已有的类。
3 抽象工厂模式
3.0 需求
工厂模式符合开闭原则,因此每个类也就只能创建一个实际功能类。也就是说一个实际功能类就对应了一个创建类。
而抽象工厂模式,则是组合了简单工厂模式和工厂模式:也就是一个创建者类可以创建不同的实际功能类。
例如实际功能类有版本差别的时候,就比如说,gnu有不同的版本,因此一个gnu的创建类,可以创建不同版本的gnu。
因此每一个创建类其实就是简单工厂模式。
3.1 实现
代码同样又臭又长,折叠了。
其实现过程就是将同种产品不同版本号的对象,使用简单工厂模式来封装。
//基类 class AB_Compiler { public: virtual void do() = 0; }; //派生类1 class Gnu_1_0 : public AB_Compiler { public: virtual void do(); }; class Gnu_2_0 : public AB_Compiler { public: virtual void do(); }; //派生类1 class Clang: public AB_Compiler { public: virtual void do(); }; //创建类基类 class Creator { public: virtual AB_Compiler* create(/*参数*/)=0; }; // 对应的创建类 class GunCreator :public Creator { public: virtual AB_Compiler* create(/*参数*/); }; // 对应的创建类 class ClangCreator :public Creator { public: virtual AB_Compiler* create(/*参数*/); };
3.2 缺点
和简单工厂模式一样,不符合开闭原则了,因此当有新版本的gnu加入的时候,gnu的创建类内部代码就需要修改。
4 总结
三种工厂模式的意义都在于,将new对象的过程封装起来,其原因和目的可能在于:
- 无法了解类的源码,类的源码由其他人维护,因此我们使用其他人提供的同一的接口来创建对象。
- 创建对象的过程可能会改变,例如,在创建对象的时候打个log,工厂模式可以不重载new实现。
工厂模式是迪米特法则的很好应用,同时也是在违背开闭原则时候的改变。