目录
工厂方法模式(Factory Pattern)
前言
工厂方法模式分为三种:普通工厂模式 多个工厂方法模式 静态工厂方法模式
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象
优点: 1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
使用场景: 1、日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。 2、数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。 3、设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。
注意事项:作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
1.普通工厂模式
就是建立一个工厂类,对实现了同一接口的产品类进行实例的创建
demo:
//发送短信和邮件的接口
/**
* Created by yjl on 2020/7/27.
* 工厂模式之普通工厂模式
* 工厂模式介绍地址:https://blog.csdn.net/qq_27471405/article/details/107607047
* 微信公众号:zygxsq
*/
//发送短信和邮件的接口
public interface Sender {
public void Send();
}
//发送邮件的实现类
/**
* Created by yjl on 2020/7/27.
* 工厂模式之普通工厂模式
* 工厂模式介绍地址:https://blog.csdn.net/qq_27471405/article/details/107607047
* 微信公众号:zygxsq
*/
//发送邮件的实现类
public class MailSender implements Sender {
public void Send() {
//这里写发送邮件的业务逻辑
System.out.println("发送邮件!");
}
}
//发送短信的实现类
/**
* Created by yjl on 2020/7/27.
* 工厂模式之普通工厂模式
* 工厂模式介绍地址:https://blog.csdn.net/qq_27471405/article/details/107607047
* 微信公众号:zygxsq
*/
//发送短信的实现类
public class SmsSender implements Sender {
public void Send() {
//这里写发送短信的业务逻辑
System.out.println("发送短信!");
}
}
//创建工厂类
/**
* Created by yjl on 2020/7/27.
* 工厂模式之普通工厂模式
* 工厂模式介绍地址:https://blog.csdn.net/qq_27471405/article/details/107607047
* 微信公众号:zygxsq
*/
//创建工厂类
public class SendFactory {
//工厂方法
public Sender produce(String type) {
if ("mail".equals(type)) {
return new MailSender();
} else if ("sms".equals(type)) {
return new SmsSender();
} else {
System.out.println("请输入正确的类型!");
return null;
}
}
}
//测试类
/**
* Created by yjl on 2020/7/27.
* 工厂模式之普通工厂模式
* 工厂模式介绍地址:https://blog.csdn.net/qq_27471405/article/details/107607047
* 微信公众号:zygxsq
*/
//测试类
public class FactoryTest {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produce("mail");
sender.Send();
}
}
运行结果:
2、多个工厂方法模式
是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。
//将上面的代码做下修改,改动下SendFactory类就行
//这个就不用根据用户传的字符串类创建对象了
demo:
//创建工厂类
/**
* Created by yjl on 2020/7/27.
* 工厂模式之多个工厂方法模式
* 工厂模式介绍地址:https://blog.csdn.net/qq_27471405/article/details/107607047
* 微信公众号:zygxsq
*/
//创建工厂类
public class SendFactory {
public Sender produceMail(){
return new MailSender();
}
public Sender produceSms(){
return new SmsSender();
}
}
//测试类
/**
* Created by yjl on 2020/7/27.
* 工厂模式之多个工厂方法模式
* 工厂模式介绍地址:https://blog.csdn.net/qq_27471405/article/details/107607047
* 微信公众号:zygxsq
*/
//测试类
public class FactoryTest {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produceMail();
sender.Send();
}
}
运行结果:
3、静态工厂方法模式
将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。
demo:
//创建工厂类
/**
* Created by yjl on 2020/7/27.
* 工厂模式之静态工厂方法模式
* 工厂模式介绍地址:https://blog.csdn.net/qq_27471405/article/details/107607047
* 微信公众号:zygxsq
*/
//创建工厂类
public class SendFactory {
public static Sender produceMail(){
return new MailSender();
}
public static Sender produceSms(){
return new SmsSender();
}
}
//测试类
/**
* Created by yjl on 2020/7/27.
* 工厂模式之静态工厂方法模式
* 工厂模式介绍地址:https://blog.csdn.net/qq_27471405/article/details/107607047
* 微信公众号:zygxsq
*/
//测试类
public class FactoryTest {
public static void main(String[] args) {
Sender sender = SendFactory.produceMail();
sender.Send();
}
}
运行结果:
4、抽象工厂模式(Abstract Factory)
工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?就用到抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。
demo:
将之前的创建工厂类拆开,用抽象类的子类来创建不同的工厂类
//给工厂类先建一个接口
/**
* Created by yjl on 2020/7/27.
* 工厂模式之抽象工厂模式
* 工厂模式介绍地址:https://blog.csdn.net/qq_27471405/article/details/107607047
* 微信公众号:zygxsq
*/
//给工厂类一个接口
public interface Provider {
public Sender produce();
}
//两个工厂的实现类
//邮件服务工厂实现类
/**
* Created by yjl on 2020/7/27.
* 工厂模式之抽象工厂模式
* 工厂模式介绍地址:https://blog.csdn.net/qq_27471405/article/details/107607047
* 微信公众号:zygxsq
*/
//邮件服务工厂实现类
public class SendMailFactory implements Provider {
public Sender produce() {
return new MailSender();
}
}
//短信服务工厂实现类
/**
* Created by yjl on 2020/7/27.
* 工厂模式之抽象工厂模式
* 工厂模式介绍地址:https://blog.csdn.net/qq_27471405/article/details/107607047
* 微信公众号:zygxsq
*/
//短信服务工厂实现类
public class SendSmsFactory implements Provider {
public Sender produce() {
return new SmsSender();
}
}
//测试类
public class FactoryTest {
public static void main(String[] args) {
Provider provider = new SendMailFactory();
Sender sender = provider.produce();
sender.Send();
}
}
运行结果:
注:这个模式的好处就是,如果你现在想增加一个功能:发送及时信息,则只需做一个实现类实现Sender接口,同时做一个工厂类,实现Provider接口,就OK了,无需去改动现成的代码。这样做,拓展性较好
高级使用
在我们实际web项目使用中,是通过三层架构dao、service、Controller来进行接口调用的。
在前端调用Controller层的时候,后端怎么知道前端想使用哪个工厂呢?
这就需要前端和后端约定,传递一个字符串进来,每个字符串代表要去调用不同的工厂类
当前端传递规定的字符串进来后,我们后端可以有多种方法实现
1、通过if...else...来判断
demo
其他方法同抽象工厂模式,我们只是增加了FactoryaaaController
//Controller层
/**
* Created by yjl on 2020/7/27.
* 工厂模式之高级使用
* 工厂模式介绍地址:https://blog.csdn.net/qq_27471405/article/details/107607047
* 微信公众号:zygxsq
*/
@RestController
@RequestMapping(value = "aaa",method = RequestMethod.GET)
public class FactoryaaaController {
@ResponseBody
@RequestMapping("/getFactory")
public void getaaa(String projectName,String aaa,String bbb){
//通过前端传递指定projectName来判断
if("email".equals(projectName)){
Provider provider = new SendMailFactory();
Sender sender = provider.produce();
sender.Send();
}else if ("sms".equals(projectName)){
Provider provider = new SendSmsFactory();
Sender sender = provider.produce();
sender.Send();
}
}
}
运行结果:
这种方法会遇到之前类似的问题,就是如果接下来又增加了一个发彩信的工厂或者其他后端工程师增加了另外的工厂,那么就会改动Controller里的代码,我们要求做的是解耦,尽量横向拓展,不动这里的代码,那么就需要用到第二种方法
2、通过注解,切面编程
demo:
//ProjectName
package cn.zygxsq.design.config;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Created by yjl on 2020/7/27.
*/
@Documented
@Retention(RUNTIME)
@Target({ElementType.TYPE})
public @interface ProjectName {
String name() default "";
}
//SpringContextUtil
/**
* Created by yjl on 2020/7/27.
*/
@Component
public class SpringContextUtil implements ApplicationContextAware {
private static Logger logger = LoggerFactory.getLogger(SpringContextUtil.class);
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (SpringContextUtil.applicationContext == null) {
SpringContextUtil.applicationContext = applicationContext;
}
logger.info("初始化ApplicationContext:" + applicationContext);
}
/**
* @return org.springframework.context.ApplicationContext
* @description 获取applicationContext
* @params []
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* @return java.lang.Object
* @description 通过beanName获取Bean
* @params [name]
*/
public static Object getBean(String beanName) {
return getApplicationContext().getBean(beanName);
}
/**
* @return T
* @description 通过class获取Bean
* @params [clazz]
*/
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
/**
* @return T
* @description 通过beanName和class返回指定的Bean
* @params [name, clazz]
*/
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
/**
* @return Map<String, T>
* @description 获取指定类型的所有bean实例
* @params [clazz]
*/
public static <T> Map<String, T> getBeansByType(Class<T> clazz) {
Map<String, T> instances = getApplicationContext().getBeansOfType(clazz);
return instances;
}
/**
* @return T
* @description 根据名称获取bean实例
* @params [clazz, markCode]
*/
public static <T> T getBeanByProjectName(Class<T> clazz, String projectName) throws Exception{
Map<String, T> instances = getBeansByType(clazz);
if (instances.isEmpty()) {
logger.info("未获取到类型[" + clazz + "]Bean列表!");
return null;
}
// logger.info("获取类型[" + clazz + "]Bean列表:" + instances);
for (String beanName : instances.keySet()) {
T instance = instances.get(beanName);
ProjectName project = instance.getClass().getAnnotation(ProjectName.class);
if (project.name().equals(projectName)) {
return instance;
}
}
logger.info("所有的Bean列表["+instances.toString()+"]不存在您要找的ProjectName(name=""+projectName+"")注解");
throw new Exception("所有的Bean列表["+instances.toString()+"]不存在您要找的ProjectName(name=""+projectName+"")注解");
//return null;
}
}
//FactorybbbController
/**
* Created by yjl on 2020/7/27.
* 工厂模式之高级使用
* 工厂模式介绍地址:https://blog.csdn.net/qq_27471405/article/details/107607047
* 微信公众号:zygxsq
*/
@RestController
@RequestMapping(value = "bbb",method = RequestMethod.GET)
public class FactorybbbController {
@ResponseBody
@RequestMapping("/getFactorybbb")
public void getbbb(String projectName,String aaa,String bbb){
//通过前端传递指定projectName来判断
try {
if (projectName==null){
projectName="sms";
}
Provider provider = SpringContextUtil.getBeanByProjectName(Provider.class, projectName);
Sender produce = provider.produce();
produce.Send();
} catch (Exception e) {
e.printStackTrace();
}
}
}
其他方法同抽象工厂模式,同时,我们需要在所有的实现工厂的子类加上ProjectName注解,注明这是什么工厂
/**
* Created by yjl on 2020/7/27.
* 工厂模式之高级使用
* 工厂模式介绍地址:https://blog.csdn.net/qq_27471405/article/details/107607047
* 微信公众号:zygxsq
*/
//短信服务工厂实现类
@ProjectName(name = "sms")
@Component
public class SendSmsFactory implements Provider {
public Sender produce() {
return new SmsSender();
}
}
/**
* Created by yjl on 2020/7/27.
* 工厂模式之高级使用
* 工厂模式介绍地址:https://blog.csdn.net/qq_27471405/article/details/107607047
* 微信公众号:zygxsq
*/
//邮件服务工厂实现类
@ProjectName(name = "mail")
@Component
public class SendMailFactory implements Provider {
public Sender produce() {
return new MailSender();
}
}
运行结果:
上述的优点就是,当这个项目有好多不同后端工程师去实现的时候,你实现了邮件和短信服务,其他工程师实现了其他的服务,那么你们只要在各自的子类工厂加上自己的注解ProjectName就可以了,然后让前端传入指定的projectname,这样你们就不用修改Controller层的方法,避免冲突之类的问题,就可以达到相同的功能效果。
工厂模式的相关代码,已经放在github上了,大家可以下载下来测试和使用:
参考文章
https://www.runoob.com/design-pattern/factory-pattern.html
感谢原作者的分享,让技术人能够更快的解决问题