public class ResourceLoader {
public Resource load(String filePath) {
String prefix = getResourcePrefix(filePath);
Resource resource = null;
if("http".equals(type)){
// ..发起请求下载资源... 可能很复杂
return new Resource(url);
} else if ("file".equals(type)) {
// ..建立流,做异常处理等等
return new Resource(url);
} else if ("classpath".equals(type)) {
// ...
return new Resource(url);
} else {
return new Resource("default");
}
return resource;
}
private String getPrefix(String url) {
if(url == null || "".equals(url) || !url.contains(":")){
throw new ResourceLoadException("此资源url不合法.");
}
String[] split = url.split(":");
return split[0];
}
}
在上边的案例中,存在很多的if分支,如果分支数量不多,且不需要扩展,这样的编写方式当然没错,然而在实际的工作场景中,我们的业务代码可能会很多,分支逻辑也可能十分复杂,这个时候简单工厂设计模式就要发挥作用了。
我们可以看到不管有多少个分支逻辑,他的本质就是一个,创造一个资源产品,我们只需要创建一个工厂类,将创建资源的能力交给工厂(不管其中如何实现)即可:
public class ResourceFactory {
public static Resource create(String type,String url){
if("http".equals(type)){
// ..发起请求下载资源... 可能很复杂
return new Resource(url);
} else if ("file".equals(type)) {
// ..建立流,做异常处理等等
return new Resource(url);
} else if ("classpath".equals(type)) {
// ...
return new Resource(url);
} else {
return new Resource("default");
}
}
}
有了上边的工厂类,我们将【创建资源产品】这个单一的能力赋予产品工厂,这样能更好的符合单一原则。有了工厂之后,我们的主要逻辑就会简化:
public class ResourceLoader {
public Resource load(String url){
// 1、根据url获取前缀
String prefix = getPrefix(url);
// 2、根据前缀处理不同的资源
return ResourceFactory.create(prefix,url);
}
这就是简单工厂设计模式,提取一个工厂类,工厂会根据传入的不同的类型,创建不同的产品,好处如下:将创建对象的过程交给工厂类、其他业务需要某个产品时,直接使用create(方法名字不重要)创建即可,这样的好处是:
1、工厂将创建的过程进行封装,不需要关系创建的细节,更加符合面向对象思想
2、这样主要的业务逻辑不会被创建对象的代码干扰,代码更易阅读
3、产品的创建可以独立测试,更将容易测试
4、独立的工厂类只负责创建产品,更加符合单一原则
q:但是有的人会问,如果需要修改或者添加新的功能,我们还是要修改源代码呀,这不符合开闭原则呀?
public interface IResourceLoader {
Resource load(String url);
}
并为每一种资源创建与之匹配的实现:实现接口的实现类
public class ClassPathResourceLoader implements IResourceLoader {
@Override
public Resource load(String url) {
// 中间省略复杂的创建过程
return new Resource(url);
}
}
}
public class FileResourceLoader implements IResourceLoader {
@Override
public Resource load(String url) {
// 中间省略复杂的创建过程
return new Resource(url);
}
}
public class HttpResourceLoader implements IResourceLoader {
@Override
public Resource load(String url) {
// 中间省略复杂的创建过程
return new Resource(url);
}
实际上,这就是工厂方法模式的典型代码实现。这样当我们新增一种读取资源的方式时,只需要新增一个实现,并实现 IResourceLoader 接口即可。所以,工厂方法模式比起简单工厂模式更加符合开闭原则。
当然有人就会说了这有什么用能呢,到时候使用的时候还不是需要如下的方式吗?这个工厂不就是来添乱的吗?
public class ResourceLoader {
public Resource load(String url){
// 1、根据url获取前缀
String prefix = getPrefix(url);
ResourceLoader resourceLoader = null;
// 2、根据前缀选择不同的工厂,生产独自的产品
// 版本一
if("http".equals(prefix)){
resourceLoader = new HttpResourceLoader();
} else if ("file".equals(prefix)) {
resourceLoader = new FileResourceLoader();
} else if ("classpath".equals(prefix)) {
resourceLoader = new ClassPathResourceLoader()
} else {
resourceLoader = new DefaultResourceLoader();
}
return resourceLoader.load();
}
不要急,我们为每个产品引入了工厂,却发现需要为创建工厂这个行为付出代价,在创建工厂这件事上,仍然不符合开闭原则,为了解决上述的问题我们又不得不去创建一个工厂的缓存来统一管理工厂实例,以后使用工厂会更加的简单,代码如下:
private static Map<String,IResourceLoader> resourceLoaderCache = new HashMap<>(8);
// 版本二
static {
resourceLoaderCache.put("http",new HttpResourceLoader());
resourceLoaderCache.put("file",new FileResourceLoader());
resourceLoaderCache.put("classpath",new ClassPathResourceLoader());
resourceLoaderCache.put("default",new DefaultResourceLoader());
}
事实上,ResourceLoader的核心方法就可以简化成这个样子了:
public class ResourceLoader {
public Resource load(String url){
// 1、根据url获取前缀
String prefix = getPrefix(url);
return resourceLoaderCache.get(prefix).load(url);
}
当然你如果觉得还是不够,你觉得修改需求还是不够灵活,仍然需要修改static中的代码,我们可以这样做,搞一个配置文件如下,将我们的工厂类进行配置,如下:
http=com.ydlclass.factoryMethod.resourceFactory.impl.HttpResourceLoader
file=com.ydlclass.factoryMethod.resourceFactory.impl.FileResourceLoader
classpath=com.ydlclass.factoryMethod.resourceFactory.impl.ClassPathResource
Loader
default=com.ydlclass.factoryMethod.resourceFactory.impl.DefaultResourceLoad
er
这样我们可以在static中这样编写代码,让我完全满足开闭原则:
public class ResourceLoader {
static {
//读取配置文件
InputStream inputStream = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("resourceLoader.properties");
Properties properties = new Properties();
try {
properties.load(inputStream);
//循环遍历配置文件数据 加入到map中
for (Map.Entry<Object,Object> entry : properties.entrySet()){
String key = entry.getKey().toString();
Class<?> clazz = Class.forName(entry.getValue().toString());
IResourceLoader loader = (IResourceLoader)
clazz.getConstructor().newInstance();
resourceLoaderCache.put(key,loader);//加入到map中
}
} catch (IOException | ClassNotFoundException | NoSuchMethodException |
InstantiationException |
IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
public Resource load(String url){
// 1、根据url获取前缀
String prefix = getPrefix(url);
return resourceLoaderCache.get(prefix).load(url);//根据不同的前缀从map中取不同的实现类(工厂)
}
以后我们想新增或删除一个resourceLoader只需要写一个类实现IResourceLoader接口,并且在配置文件中进行配置即可。此时此刻我们已经看不到if-else的影子了。
我们的代码中产品是简单单一的类,事实上,在工作中,我们的产品可能是及其复杂的,我们同样需要对整个产品线进行抽象
public abstract class AbstractResource {
private String url;
public AbstractResource(){}
public AbstractResource(String url) {
this.url = url;
}
protected void shared(){
System.out.println("这是共享方法");
}
/**
* 每个子类需要独自实现的方法
* @return 字节流
*/
public abstract InputStream getInputStream();
}
具体的产品需要继承这个抽象类:
产品类:
public class ClasspathResource extends AbstractResource {
public ClasspathResource() {
}
public ClasspathResource(String url) {
super(url);
}
@Override
public InputStream getInputStream() {
return null;
}
}
其他产品同理,我们的工厂类也需要面向产品的抽象进行编程了:
工厂类:
public class ClassPathResourceLoader implements IResourceLoader {
@Override
public AbstractResource load(String url) {
// 中间省略复杂的创建过程
AbstractResource classpathResource = new ClasspathResource(url)
return classpathResource;
}
}
我们编写测试用例进行测试:
@Test
public void testFactoryMethod(){
String url = "Classpath://D://a.txt";
ResourceLoader resourceLoader = new ResourceLoader();//核心类 用于调用load方法,通过load方法返回不同类型的工厂
AbstractResource resource = resourceLoader.load(url); //产品抽象类(也可以是接口)AbstractResource,依赖于抽象而不依赖于具体的实现!!
// resourceLoader.load(url)它会根据URL前缀类型,走不同的工厂类,工厂类的方法再返回和类型对应的产品对象
//代码层面没有看到具体哪一种类型的工厂类和产品类,
//只有产品接口的类型和一个用于加载工厂的核心类ResourceLoader 体会依赖于抽象而不依赖于具体的实现!!
log.info("resource --> {}",resource.getClass().getName());//返回的是具体类型的产品类ClasspathResource
}
简单工厂模式(Simple Factory Pattern):
工厂方法模式(Factory Method Pattern):
抽象工厂模式(Abstract Factory Pattern):
总结:
1)Calendar
jdk中的日历类可以根据时区、地点创建一个满足当时需求的日历实例,这就是一个简单工厂:
2)DateFormat
DateFormat同样可以根据类型和地域生成一个满足本地特色的Date格式化工具:
DateFormat dateInstance = DateFormat.getDateInstance(DateFormat.FULL, Locale.CHINA);
log.info("date-->{}", dateInstance.format(new Date()));
spring中的bean工厂就是一个典型的简单工厂设计模式:
beanFactory.getBean("userService");
FactoryBean提供了三个方法,其中getObject就是一个典型的工厂方法,
FactoryBean定制bean的创建过程,我们将工厂bean注入容器,有容器统一管理工厂对象,再有工厂对象创建具体的bean。
public interface FactoryBean<T> {
@Nullable
T getObject() throws Exception;
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
Spring–BeanFactory和FactoryBean区别
mybatis中有很多Factory结尾的类,也是使用工厂设计模式如:
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel
level);
SqlSession openSession(ExecutorType execType, Connection connection);
Configuration getConfiguration();
}
该类可以根据接口类型生成对应的具体实现,也是一种代理,核心方法
newInstance:
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new
Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession,
mapperInterface, methodCache);
return newInstance(mapperProxy);
}
假设您正在开发一个支持多种数据库(例如 MySQL、PostgreSQL、Oracle 等)的应用程序。根据配置文件或用户选择的数据库类型,您需要创建相应类型的数据库连接。这是一个工厂模式可以发挥作用的场景。首先,定义一个数据库连接接口:
public interface DatabaseConnection {
Connection getConnection();
}
然后,实现多种数据库连接类型:
public class MySQLConnection implements DatabaseConnection {
@Override
public Connection getConnection() {
// 实现 MySQL 连接的创建逻辑
}
}
public class PostgreSQLConnection implements DatabaseConnection {
@Override
public Connection getConnection() {
// 实现 PostgreSQL 连接的创建逻辑
}
}
public class OracleConnection implements DatabaseConnection {
@Override
public Connection getConnection() {
// 实现 Oracle 连接的创建逻辑
}
}
接下来,创建一个工厂类,用于根据数据库类型创建相应的数据库连接实例:
public class DatabaseConnectionFactory {
public static DatabaseConnection createDatabaseConnection(String
databaseType) {
if (databaseType == null) {
throw new IllegalArgumentException("Database type cannot be null.");
}
if (databaseType.equalsIgnoreCase("MySQL")) {
return new MySQLConnection();
} else if (databaseType.equalsIgnoreCase("PostgreSQL")) {
return new PostgreSQLConnection();
} else if (databaseType.equalsIgnoreCase("Oracle")) {
return new OracleConnection();
} else {
throw new IllegalArgumentException("Invalid database type: " +
databaseType);
}
}
}
现在,您可以使用工厂类根据配置或用户选择创建相应的数据库连接实例:
DatabaseConnection connection =
DatabaseConnectionFactory.createDatabaseConnection("MySQL");
Connection conn = connection.getConnection();
通过工厂设计模式,您可以轻松地在运行时根据需要创建不同类型的数据库连接,提高代码的可扩展性和灵活性。
当创建逻辑比较复杂,是一个“大工程”的时候,我们就应该考虑使用工厂模式,封装对象的创建过程,将对象的创建和使用相分离。
何为创建逻辑比较复杂呢?我总结了下面两种情况。
日常工作中很多场景都可以使用工厂设计模式,如使用不同的支付方式支付,使用不同的登录器登录等等。