工厂模式类型
1、简单工厂模式
2、工厂方法模式
3、抽象工厂模式
面向接口编程
面向接口编程
1、每个模块负责自己的职责(单一职责),各个模块之间通过接口隔离
2、每个模块都应该"承诺"自己对外暴露的接口是不变的。当模块内部发生变化时,其他模块是不需要知道的。这便是依赖于抽象而不依赖于实现(依赖倒置原则)
3、上层模块只知道下层模块暴露出的接口即可,至于实现细节不需要也不应该知道。
举个简单的例子来简单的说明上面三个条件。
三国时期,曹操和刘备各自有自己的军队。他们分别管理蜀国和魏国(职责单一)。各国之间只能通过外交官(接口隔离)进行传话。各国的外交官这个人对外是不变的,当有的小国换了君主,其他国家是不用知道的,有什么事只用找这个外交官商量即可(依赖抽象不依赖实现)。
概念
产品:类
抽象产品:抽象类、接口
产品簇:多个内在联系的产品(比如 小米手机 小米手环)
产品等级:
作者:可以理解为开发者 如:开发mybatis的为作者 文中也以服务器端来表示
用户:可以理解为使用者 如:使用mybatis的程序员为用户 文中也以客户端来表示
简单工厂模式
看如下一段代码
//抽象产品
interface Food{
void eat();
}
// 具体产品
class Hamburger implements Food{
@Override
public void eat() {
System.out.println("吃汉堡包!");
}
}
/**==============================================================
*/
public class AppTest {
public static void main(String[] args) {
Food f = new Hamburger();
f.eat();
}
}
虚线以上为作者开发,虚线以下为用户调用。
如果有一天作者突然做出了修改将Hamburger变成了Hamburger2,此时用户就无法使用了。
interface Food{
void eat();
}
class Hamburger2 implements Food{
@Override
public void eat() {
System.out.println("吃汉堡包!");
}
}
/**==============================================================
*/
public class AppTest {
public static void main(String[] args) {
//Cannot resolve symbol 'Hamburger'
Food f = new Hamburger();
f.eat();
}
}
那么用户这边也需要跟着修改,因为耦合度太高。面向接口编程本身就是解耦的,用户只需要知道接口,至于你是hamburger1还是hamburger2不是用户所关心的。作者把细节暴露给用户就是违反了迪米特法则。
这种设计相当脆弱!为什么呢?因为,只要作者修改了具体产品的类名,那么客户端代码,也要随着一起改变。这样服务器端代码和客户端代码就是耦合的!我们希望的效果是,无论服务器端代码如何修改,客户端代码都应该不知道,不用修改客户端代码。
针对以上问题修改
使用简单工厂模式
class FoodFactory{
public static Food getFood(int n){
Food food =null;
switch (n){
case 1:
food = new Hamburger();
break;
}
return food;
}
}
/**==============================================================
*/
public class AppTest {
public static void main(String[] args) {
Food f = FoodFactory.getFood(1);
f.eat();
}
}
分析
这样做的好处是如果作者将Hamburger改成Hamburger2或者Hamburger3,用户只用知道getFood(1)中的1对应的是Hamburger,而不用去管它是Hamburger2还是Hamburger3。面向接口编程说的不是java类中的interface。而是上层向下层暴露的接口,如上面代码中的静态方法getFood。这样就隔离了变化,也是符合依赖倒置原则,调用者不用依赖细节,而应该依赖抽象。
面对变化
此时用户觉得汉堡太腻了,想要再添加一个食物——面条呢?用户先得创建一个面条类
class Noddle implements Food{
@Override
public void eat() {
System.out.println("吃面条");
}
}
然后在简单工厂中添加一个case条件分支即可
class FoodFactory{
public static Food getFood(int n){
Food food =null;
switch (n){
case 1:
food = new Hamburger();
break;
//添加面条的条件分支
case 2:
food = new Noddle();
break;
}
return food;
}
}
很显然,简单工厂是不利于拓展的,每次新加一个新的食物都需要去修改简单工厂中的switch语句,违背了“对修改关闭,对拓展开放”的原则。
类图展示
最终类图展示如下:
用户不属于类图的范畴,这里加上只是为了方便理解,对比类图和面向接口编程的图就很容易理解了。
总结
【简单工厂】
优点
1、把具体产品的类型,从客户端代码中解耦出来 。
2、服务器端,如果修改了具体产品的类型,客户端也无影响
这便符合了“面向接口编程”的思想
缺点
1、客户端不得不死记硬背那些常量与具体产品的映射关系 比如:1对应Hamburger
2、如果客户端产品特别多,则简单工厂就会变得臃肿。比如有100个具体产品,则需要在简单的switch中写100多个case!
3、最重要的是,变化来了:客户端需要拓展具体产品的时候,势必需要修改简单工厂中的代码,这样便违背了“开闭原则”
工厂方法模式
为了使简单工厂更方便的拓展,在上面简单工厂的例子中升级成为工厂方法模式。首先,我们需要从代码层面去明确简单工厂为什么不方便做拓展,答案是因为它只有一个工厂,所有的食物都必须在这一个工厂内进行实例化,那就无法避免的要使用条件分支确定食物类型。
将简单工厂方式改为工厂方法方式
1、去掉简单工厂中的FoodFactory
2、创建一个工厂接口
interface FoodFactory {
Food getFood();
}
3、创建生产汉堡包的工厂并实现接口
class HamburgerFactory implements FoodFactory{
@Override
public Food getFood() {
return new Hamburger();
}
}
4、调用
public class AppTest {
public static void main(String[] args) {
FoodFactory ff =new HamburgerFactory();
Food f=ff.getFood();
f.eat();
}
}
吃汉堡包!
工厂方法创建工厂,工厂内部进行实例化。这样每新加一个食物的时候,就需要添加一个与之相关的工厂。
比如:添加大米Rice
class Rice implements Food{
@Override
public void eat() {
System.out.println("吃大米");
}
}
class RiceFactory implements FoodFactory{
@Override
public Food getFood() {
return new Rice();
}
}
public class AppTest {
public static void main(String[] args) {
FoodFactory ff =new RiceFactory();
Food f=ff.getFood();
f.eat();
}
}
吃大米
这样就把简单工厂里的修改转变为拓展了。除此之外,在客户端调用时,也不用再去记hamburger对应什么,Noddle对应什么,Rice对应什么。直接见名知意的调用即可。
类图展示
总结
【工厂方法】
优点
1、仍然具有简单工厂的优点:服务器端修改了具体产品以后,客户端无影响
2、当客户端需要拓展一个新的产品时,不需要修改作者原来的代码,只是拓展一个新的工厂而已
问题
我们一开始创建简单工厂就是了让客户端感受不到服务器端的变化(即不论Hamburger1或Hamburger2客户端都不要去了解),现在如果服务器把RiceFactory改为了RiceFactory1或RiceFactory2,客户端仍然需要做相应的修改,这不是白忙活了吗?
这里我们可以看面向接口编程中的第2条:
每个模块都应该"承诺"自己对外暴露的接口是不变的。当模块内部发生变化时,其他模块是不需要知道的。这便是依赖于抽象而不依赖于实现(依赖倒置原则)
每个模块应该"承诺"对外暴露的接口是不变的。当然,如果确实需要改变的,那就只能同步进行修改。
缺点
1、每一个实体类都需要一个工厂类与之对应,这里的产品是Food,下一次是Drink呢,过多的产品等级将会导致类爆炸。
抽象工厂模式
当有过多产品等级时,使用抽象工厂模式会比工厂方法模式更适用。
此处普及一下产品等级和产品簇的概念
同样颜色的为产品簇,具有一定联系,如图中,红色的电器均属于华为。产品等级为同一类产品,电冰箱为一个产品等级,电视机为另外一个产品等级。如图中有5个产品等级。
以工厂方法类图为例
先创建基本的产品
//抽象产品
interface Drink{
void drink();
}
interface Food{
void eat();
}
// 产品
class Rice implements Food {
@Override
public void eat() {
System.out.println("吃大米");
}
}
class Hamburger implements Food {
@Override
public void eat() {
System.out.println("吃汉堡包!");
}
}
class Cola implements Drink {
@Override
public void drink() {
System.out.println("喝可乐");
}
}
class IcePeak implements Drink {
@Override
public void drink() {
System.out.println("喝冰峰");
}
}
①工厂方法模式
// 工厂相关
interface FoodFactory {
Food getFood();
}
interface DrinkFactory{
Drink getDrink();
}
class HamburgerFactory implements FoodFactory{
@Override
public Food getFood() {
return new Hamburger();
}
}
class RiceFactory implements FoodFactory{
@Override
public Food getFood() {
return new Rice();
}
}
class ColaFactory implements DrinkFactory{
@Override
public Drink getDrink() {
return new Cola();
}
}
class IcePeakFactory implements DrinkFactory {
@Override
public Drink getDrink() {
return new IcePeak();
}
}
②抽象工厂模式
abstract class Factory{
abstract Food getFood();
abstract Drink getDrink();
}
class KFCfFactory extends Factory{
@Override
Food getFood() {
return new Hamburger();
}
@Override
Drink getDrink() {
return new Cola();
}
}
class QiansionFactory extends Factory{
@Override
Food getFood() {
return new Rice();
}
@Override
Drink getDrink() {
return new IcePeak();
}
}
从对比中就能看出在产品等级多的情况下,抽象工厂的类更少,或者说它本身就很契合于多产品等级的。在工厂方法模式中,每一个产品都有自己的工厂——大米工厂、汉堡工厂、可乐工厂、冰峰工厂。但是这些工厂都有一些特点,大米和汉堡工厂都是食物,可乐和冰峰工厂都是饮品。食物和饮品这就是两个产品等级了,那么生产食物和饮品工厂既不能叫做食物工厂也不能叫做饮品工厂,所以可以用抽象工厂来指代,这个抽象工厂技能生产食物也能生产饮品。
类图展示
可以看出,如果一个工厂里只生产一个产品等级(如:getFood()),那就是工厂方法,如果一个工厂里生产多个产品等级(如:getFood(),getDrink())那就是抽象工厂。
拓展性(增加产品簇)
新增一类食品(HotDryNoodles,热干面)和饮品(eggnog,蛋酒)
class HotDryNoodles implements Food{
@Override
public void eat() {
System.out.println("吃热干面");
}
}
class Eggnog implements Drink{
@Override
public void drink() {
System.out.println("喝蛋酒");
}
}
class WHFactory extends Factory {
@Override
Food getFood() {
return new HotDryNoodles();
}
@Override
Drink getDrink() {
return new Eggnog();
}
}
class diet{
public static void taste(Factory ff){
Food f = ff.getFood();
Drink d = ff.getDrink();
System.out.println("品尝:");
f.eat();
d.drink();
}
}
public class AppTest {
public static void main(String[] args) {
Factory ff = new WHFactory();
diet.taste(ff);
}
}
品尝:
吃热干面
喝蛋酒
总结
【抽象工厂】
优点
1、仍然有简单工厂和工厂方法的特点
2、更重要的是,抽象工厂把工厂类的数量减少了!无论多少个产品等级,工厂就一套
问题
1.为什么KFCFactory中,就必须是Humburger搭配Cola呢?为什么不能是Rice搭配Cola呢?
抽象工厂中,可以生产多个产品,但这多个产品之间,必须有内在联系。即必须为产品簇
缺点
1、当产品等级发生变化时(增加产品等级、删除产品等级),都要引起所有工厂代码的修改,这就违反了“开闭原则”。
三种工厂模式场景比较
当产品不需要扩充时,使用简单工厂
当产品等级只有一个的时候,使用工厂方法
当产品等级比较固定时,使用抽象工厂。
工厂模式应用
Calendar
Calendar是java的日历类,是简单工厂的代表。
Calendar calendar = Calendar.getInstance();
关注getInstance()方法中的createCalendar方法
这里截取createCalendar关键部分
可以说,这很简单工厂了。
LoggerFactory
log4j是我们平常经常使用的日志工具,其中的日志工厂就是用工厂方法模式来实现的。此处产品等级只有一个,就是Logger。
Logger logger = LoggerFactory.getLogger("logger");
可以关注这个getLogger的方法中的getILoggerFactory
根据条件的不同得到不同的工厂。
BeanFactory
spring的核心Bean工厂自然是不能的缺席工厂模式了。
从图中可以看出beanFactory里存在多个返回值,可以确定它的产品等级是多个的。
而每一个接口的实现最少都有5个实现类,这5个实现类可以看成5个工厂,每个工厂都会实现里面的方法形成一系列的产品簇,是典型的抽象工厂模式。