现在我们来学习简单工厂,简单工厂的定义和类型,他的定义非常简单,有一个工厂对象决定创建出哪一种产品类的实例,
关键字创建产品,他的类型属于创建型,但是不属于GOF23种设计模式当中,这里面我也要讲一下,因为抽象工厂和工厂方法,
里面都是由简单工厂一步一步演进,那我们讲完这个之后呢,也可以形成对比,这样对大家理解是非常有益处的
我们看一下简单工厂的应用场景,首先工厂类创建的对象比较少,这种场景适合简单工厂,还有客户类只知道传入工厂类的
参数,对于如何创建对象,也就是创建对象的具体逻辑呢,不关心,那简单工厂非常简单,一会领着大家来coding的时候,
相信小伙伴们一定会理解的
我们来看一下简单工厂的优点,只需要传入一个正确的参数,就可以获取你所需要的对象,而无需知道其创建细节,
那还有判断的逻辑,可以决定在哪个场景创建一个实例,客户端可以免除直接创建的责任,那这里所说的客户端,
包括客户端应用层,后续我们都会用Test测试类来描述,这里面只是生产产品,简单工厂通过这种做法,实现了责任的
分割,提供了专门的工厂类,去创建对象,甚至不需要知道产品的类名,只需要知道对应的参数是什么,参数和类名肯定是
参数更简单,第二个是不需要知道其创建细节,我只要告诉你我需要一杯咖啡,简单工厂直接返回咖啡就行了
我们看一下简单工厂有什么缺点,工厂类的职责相对过重,增加新的产品的时候,需要修改简单工厂类的判断逻辑,
违背了开闭原则,前面讲原则的时候都有说,这些原则不能全部遵守,也不能不遵守,这是一个平衡,要把握其中的度,
根据实际的业务模型,因为工厂类集中了创建逻辑,一旦出现问题,或者出现bug,也会增加系统中类的个数,增加了
系统的复杂度,和理解难度,一旦增加新的产品,我们是不得不修改工厂的逻辑的,那在产品类型非常多的时候,
整个工厂逻辑就会过于复杂,不利于维护和扩展,那简单工厂还有一个缺点,那后面抽象工厂中
接下来我们一起来coding,我们也会查看简单工厂的UML,同时也会对源码进行解析,包括JDK的源码,
会查看mybatis的源码,找到我们学习的设计模式,以设计模式的角度来对开源代码进行一个解析
我们现在来学习工厂的相关模式,我们先来了解简单工厂,简单工厂严格意义上来说,他并不属于GOF23种设计模式的一种,
这里边一定要注意,那简单工厂像是一个编码风格,和习惯,很多人会把简单工厂认为是工厂模式,那工厂关键字相关的设计模式
在GOF当中,是分为工厂方法和抽象工厂的,那我们看一下维基百科对于设计模式的介绍,这里有一个Patterns by Type,
按照类型进行分类的一个设计模式,首先看这里Creational,这里是有5个设计模式的,分别是抽象工厂,建造者,工厂方法,
原型模式,还有单例模式,而这里是没有简单工厂模式的,简单工厂我们也是要讲的,首先从简单工厂开始,我们学习并了解之后呢,
对于工厂方法和抽象工厂,我们理解起来也会更容易一些,那首先我们创建一个包,在这里我们就会学习Creational设计模式,
在这里注意一下,简单工厂不属于GOF设计模式当中的,但是我们把它单独创建一个包,为了分类更合理,把它也放到Creational里面,
那简单工厂非常简单,我们来看一下例子,我们在学习这里面所有的设计模式的时候,首先我们描述一个业务场景,例如网上有JAVA
的视频,Python的视频,前端的视频
现在Test里面生成了一个PathonVideo,然后这两个Video继承Video,然后我们回到Test里面
这个来看就是非常清晰了,原来这个应用层client客户端,直接依赖PathonVideo和JavaVideo,现在并不是,应用层Test
测试类,他只依赖VideoFactory,而VideoFactory,创建具体的,Java视频还是Pathon视频,具体的生产过程都在VideoFactory
里面,Test只管直接使用
应用层生产视频,应用层找工厂,我直接用就可以了,我会告诉你我要什么视频,然后VideoFactory给我生产,
那我们回到代码
package com.learn.design.pattern.creational.simplefactory;
/**
* 具体的视频的属性
* 时常
* 我们在类中就不描述了
* 我们有一个生产视频的方法
* 而这个生产视频应该是一个抽象方法
* 例如JAVA课程的视频
* 和Python课程的视频
* 生产过程会有点不一样
*
*
* @author Leon.Sun
*
*/
public abstract class Video {
/**
* 所以我们现在写一个抽象方法
* 这个时候我们的类也要是抽象类才可以
* 因为拥有了抽象方法
* 这个类必然是抽象类
*
*
*/
public abstract void produce();
}
package com.learn.design.pattern.creational.simplefactory;
/**
* 紧接着我们创建一个JAVA的视频实现类
* 他继承Video这个抽象类
* 同时实现produce方法
* 现在我们填充这个实现
*
*
* @author Leon.Sun
*
*/
public class JavaVideo extends Video {
@Override
public void produce() {
System.out.println("录制Java课程视频");
}
}
package com.learn.design.pattern.creational.simplefactory;
/**
* PythonVideo他也实现Vedio
* 实现方法
*
* @author Leon.Sun
*
*/
public class PythonVideo extends Video {
@Override
public void produce() {
System.out.println("录制Python课程视频");
}
}
package com.learn.design.pattern.creational.simplefactory;
/**
*
* @author Leon.Sun
*
*/
public class VideoFactory {
/**
* 这里面我们使用Class
*
* 这个class
* 就是包名
* 后面是一个类
*
*
* @param c
* @return
*/
public Video getVideo(Class c){
/**
* 这里声明Video等于null
* 后面会说明为什么要什么Video等于null
*
*
*/
Video video = null;
try {
/**
* c.getName()获取类名
* 这里面还需要一个强转
* c.getName()是一个全的
* 包括包名和类名
* 然后获取他的一个对象
* 把它强转成父类
* 即使我们传JavaVideo或者PathonVideo
* 都可以强转成父类的
* 所以这里面Video也就赋值成功了
*
* 然后调用newInstance方法
* 来获取这个对象
* 然后强转赋值给Video
*
*
*/
video = (Video) Class.forName(c.getName()).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
/**
* 下边我们直接返回Video就可以了
*
*/
return video;
}
/**
* 首先我们要创建一个方法
* 返回值是Video
* 我们还要传一个类型type
* 根据type判断返回什么样的子类实现
* 那这个也比较简单
* 我们直接写
*
* 我们看一下工厂的具体实现
* 现在我们要加一个算法的课程
* 或者前端的课程
* 我们要修改这个类的一个实现
* 里面创建一个具体的
* 前端课程的一个value
* 也就是说随着课程的一个扩展
* 我们的类要不断地修改
* 而修改就会带来风险
* 通过修改这个类
* 来扩展具体的一个类型
* 所以他并不符合开闭原则
* 那我们再来看一下UML
* 我们可以想象一下我们的目的是为了扩展
* 那是为了对扩展开放
* 和对修改关闭
* 这个时候我们就可以使用工厂方法来演进一下
* 我们再理会一下他们之间的差异
* 之后我们再像抽象工厂演进
* 然后再体会他们之间的不同
* 那通过这个我们加深对工厂模式的理解
* 也有把这个方法写成静态方法的
* 通过静态方法来建一个简单的工厂
* 这个也是比较常见的
* 使用静态方法就直接调用就可以了
* 不用创建VideoFactory
* 因为这个方法是静态方法呢
* 我们是无法改变这个行为
* 如果没有被继承或者重写的
* 直接使用VideoFactory是OK的
* 刚刚我们对简单工厂也分析了一下
* 对于简单工厂我们还可以通过反射
* 来弥补简单工厂的扩展性
* 利用反射来演示一下简单工厂
*
*
*
*
* @param c
* @return
*/
public Video getVideo(String type){
/**
* 不区分大小写
* 这里使用switch也是OK的
*
* 这个时候判断他是一个Java视频
*
*/
if("java".equalsIgnoreCase(type)){
/**
* 直接返回一个Java视频
*
*/
return new JavaVideo();
}else if("python".equalsIgnoreCase(type)){
return new PythonVideo();
}
return null;
}
}
package com.learn.design.pattern.creational.simplefactory;
/**
* 对于Test这个类它是属于应用层的
* 我们也可以把它理解成调用类的一个client
* 所以我们接下来讲应用层代码
* 无论这个类是Test还是Client
* 在我们这里面都是一样的
* 我们创建一个Java的视频
* new一个JavaVideo
* 调用它的produce方法
* 如果我们需要JAVA的视频
* 那现在就是一个父类的引用
* 指向了子类的一个实现
* 在我们这个客户端类
* Test类里面
* 也就是说应用层类
* 它是非常依赖JavaVideo这个类的
* 例如我现在想生成PythonVideo
* 这个类肯定是需要改的
* 那现在我们在同一个包下
* 所以不需要额外的import
* 所以在import这个区域
* 体现不出来的
* 如果我们是跨包的
* 或者是需要import的时候
* 就可以看到是需要依赖的类的
* 那我们能不能不让我们的应用层依赖对应的类
* 那我们能够把生产过程放到一个类里面
* 是我们的应用层不依赖于对应的具体实现类
* 这个就是一个简单工厂的一个由来
* 现在我们就把创建具体的视频过程移植到简单工厂里面
* 那我们现在来创建这个类
*
* 简单工厂有没有什么缺点
*
*
*
* @author Leon.Sun
*
*/
public class Test {
public static void main(String[] args) {
/**
* 现在就不直接new PathonVideo了
* 我不需要直接依赖这个类
* 那这里面也就不使用匿名对象了
* 直接再new一个出来
*
* 这里面并没有具体的PathonVideo还是JavaVideo
* 如果导包的时候
* 不需要导具体的
* JavaVideo还是PythonVideo
* 我们只导入一个VideoFactory就可以了
* 现在他们都是同包的
* 不需要import也能够拿到它
* 现在看一下最新的UML类图
*
* 首先new了一个VideoFactory
* 这个毋庸置疑
*
*/
// VideoFactory videoFactory = new VideoFactory();
/**
* 通过videoFactory的getVideo方法
* 里面传一个JAVA
*
* 然后进入getVideo方法
*
* 这个时候注意Video的一个类型
* 具体的指向的是JavaVideo的一个对象
* 这个对象的具体类型是JavaVideo类型
* 同时它也是Video类型
* Video instanceof JavaVideo
* 但是他不是PathonVideo类型
* Video instanceof PathonVideo
* 然后继续
*
*
*/
// Video video = videoFactory.getVideo("java");
/**
* 如果为空就直接return
*
*/
// if(video == null){
// return;
// }
/**
* 然后这里直接调用JavaVideo的produce方法
* 这个就是简单工厂
* 非常容易理解
*
*
*/
// video.produce();
VideoFactory videoFactory = new VideoFactory();
/**
* 这里要传对应的class
* 那我们只需要传JavaVideo.class就可以了
* 这样有一个扩展性
* 从一定程度上满足开闭原则
* 如果我们新增一个前端课程
* FEVideo
* 那只需要传FEVideo的类就可以了
* 而这个工厂是不需要变化的
* 工厂我们不需要再修改他
* 我们现在来debug一下
*
*
*/
Video video = videoFactory.getVideo(JavaVideo.class);
if(video == null){
return;
}
/**
* 直接执行produce方法
*
*/
video.produce();
}
}