当前位置: 首页 > 知识库问答 >
问题:

工厂设计模式与Spring

申阳伯
2023-03-14

我想知道在Spring框架中使用依赖注入的工厂模式的当前最佳实践是什么。我想知道工厂模式在使用Spring依赖注入的情况下是否仍然适用。我做了一些搜索,看到了一些过去的讨论(依赖注入vs工厂模式),但似乎有不同的观点。

我在一些现实生活中的项目中看到使用Map来保存所有的bean,并依靠自动装配来创建这些bean。当需要bean时,它使用密钥通过map获取它。

public abstract class Service {
  //some methods
}

@Component
public class serviceA extends Service {
  //implementation
}

@Component
public class serviceB extends Service {
  //implementation
}

Map<String, Service> services; 

但是我看到这两种方法有些不同。

使用上述方法,所有bean都在应用程序启动时创建,对象的创建由框架处理。它还意味着每种类型只有一个bean。

而对于工厂模式,工厂类会根据请求创建对象。它可以为每个请求创建一个新对象。

我认为一个更深层次的问题可能是,当Spring框架在项目中使用时,它是否应该努力在类中不创建任何对象,这意味着工厂模式(或任何创建的设计模式?)不应该使用,因为Spring应该是对象依赖关系的中心处理程序?

共有2个答案

长孙承嗣
2023-03-14

我认为如果你的项目使用Spring框架,你应该使用它。尽管这取决于你的项目设计,例如,你可以在Spring国际奥委会的同时使用创意模式。e、 g当抽象层不依赖于框架时(不可知代码)

    interface ServiceFactory {
       
       Service create(String type);
    }
    
    @Component
    class SpringServiceFactory implements ServiceFactory {
    
         @Autowired private ApplicationContext context;
    
         Service create(String type) {
             return context.getBean(type)
         } 
    }

当我重构遗留的不可单元测试代码时,我也使用工厂模式,该代码也使用Spring框架来实现单元测试。

// legacy service impossible to mock
class LegacyApiClient implements Closeable {...}

@Component
class LegacyApiClientFactory {
   
    LegacyApiClient create(String endpoint) {
        return new LegacyApiClient(endpoint);
    }
}

@Component
class OtherService {
  
   
  private final String endpoint   

  private final LegacyApiClientFactory factory;

  OtherService(@Value("${post.endpoint}") String endpoint, 
         LegacyApiClientFactory factory) {...}  
  
  void doCall {
     
      try (LegacyApiClient client = factory.create(endpoint)) {
           client.postSomething();
      }
  }
   
}

.... 
  // a random unit test
  LegacyApiClient client = mock(LegacyApiClient.class)
  LegacyApiClientFactory factory = mock(LegacyApiClientFactory.class)
  OtherService service = new OtherService("http://scxsc", factory);

  when(factory.create(any())).thenReturn(client)

  service.doCall()  
....

戚同
2023-03-14

这个问题的答案可以是非常深刻和广泛的,我将尝试提供一些观点,希望能有所帮助。

首先,spring将其bean(单例)存储在ApplicationContext中。本质上这就是你所说的地图。简而言之,它允许按名称、类型等获取bean。

ApplicationContext虽然是一个非常重要的概念,但并不是整个Spring,事实上Spring框架允许更大的灵活性:

您可以说,使用映射意味着所有bean都将在应用程序开始时创建,并且有一个bean实例。

Spring有一个Lazy bean的概念,基本上支持只有当第一次需要时才实际创建bean的概念,所以Spring支持“延迟”bean初始化

Spring还允许每个类型有多个bean实例。所以这个映射更“高级”。例如,您可以创建多个接口实现,并使用声明两个为bean。只要您提供足够的信息,说明应该将哪些bean注入可能使用它们的类(例如,借助Spring中支持的限定符),您就可以开始了。此外,Spring IoC容器中还有一些特性,允许将接口的所有注册实现注入列表中:

interface Foo {}
@Component
class FooImpl1 implements Foo {}
@Component
class FooImpl2 implements Foo {}

class Client {
   @Autowired
   List<Foo> allFoos;
}

现在你说:

而对于工厂模式,工厂类会根据请求创建对象。它可以为每个请求创建一个新对象。

实际上Spring可以根据请求创建对象。并非所有bean都必须是单例的,一般来说,Spring为此有一个作用域的概念。

例如,scope prototype意味着Spring将在每次使用时创建一个bean。特别是spring以各种方式支持的一个有趣用法是将原型bean注入singleton。一些解决方案的使用方式与工厂完全相同(请阅读注释@Lookup其他解决方案则依赖于运行时自动生成的代理(如javax.inject.Provider)。原型范围bean不在应用程序上下文中,因此这里spring再次超越了简单的映射抽象。

您没有提到的最后一个特性是,有时即使对于单例,初始化也可能比使用参数调用构造函数复杂一些。Spring可以通过使用Java配置来解决这个问题:

@Configuration
public class MyConfig {
   public SomeComplicatedObject foo(@Value("...") config, Bar bar) {

       SomeComplicatedObject obj = new SomeComplicatedObject() // lets pretend this object is from some thirdparty, it only has no-op constructor, and you can't place spring annotations on it (basically you can't change it):
        obj.setConfig(config);
        obj.setBar(bar);
        return obj;
   }
}

这里的方法foo初始化对象somecomplezadobject,并返回它。这可以用来代替工厂来集成“遗留”代码(好吧,java配置远远超出了这一点,但它超出了这个问题的范围)。

所以归根结底,Spring作为一个IoC容器可以提供许多不同的方法来处理对象创建,特别是它可以做工厂设计模式提供的一切。

现在,我还要提到你的最后一句话:

我认为一个更深层次的问题可能是,当Spring框架在项目中使用时,它是否应该努力在类中不创建任何对象,这意味着工厂模式(或任何创建的设计模式?)不应该使用,因为Spring应该是对象依赖关系的中心处理程序?

实际上,在使用Spring时,您不必使用Factory模式,因为(我希望能说服您)提供了Factory可以做的一切,以及更多。

我也同意spring应该是对象依赖关系的中心处理程序(除非应用程序的某些部分也是以不同的方式编写的,所以您必须同时支持这两个:)

我不认为我们应该完全避免使用“new”,不是所有的东西都应该/可以是一个bean,但我确实看到(从我的主观经验来看,这是有争议的),你使用它的次数要少得多,而大多数对象的创建都是由Spring完成的。

我们应该避免使用任何创作设计模式吗?我不这么认为,有时候你可以选择实现“builder”设计模式,例如,它也是一种创造性的模式,但spring没有提供类似的抽象。

 类似资料:
  • 工厂-创建对象而不向客户机公开实例化逻辑,并通过公共接口引用新创建的对象。是工厂方法的简化版本 工厂方法-定义一个创建对象的接口,但让子类决定实例化哪个类,并通过公共接口引用新创建的对象。 抽象工厂-提供了创建相关对象家族的接口,而无需显式指定它们的类。 null

  • 介绍 与创建型模式类似,工厂模式创建对象(视为工厂里的产品)时无需指定创建对象的具体类。 工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一个类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型。 这个模式十分有用,尤其是创建对象的流程赋值的时候,比如依赖于很多设置文件等。并且,你会经常在程序里看到工厂方法,用于让子类类定义需要创建的对象类型

  • 本文向大家介绍PHP设计模式之工厂模式与单例模式,包括了PHP设计模式之工厂模式与单例模式的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了PHP设计模式之工厂模式与单例模式实现方法。分享给大家供大家参考,具体如下: 设计模式简单说应对某类问题而设计的解决方式 工厂模式:应对需求创建相应的对象 单例模式:只创建一个对象的实例,不允许再创建实例,节约资源(例如数据库的连接) 运行结果如下: 应

  • 简单工厂(Simple Factory) Intent 在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。 Class Diagram 简单工厂把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化。 这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。客户类往往有多个,如果不使用简单工厂,那么所

  • 4. 抽象工厂(Abstract Factory) Intent 提供一个接口,用于创建 相关的对象家族 。 Class Diagram 抽象工厂模式创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,也就是说必须一起创建出来。而工厂方法模式只是用于创建一个对象,这和抽象工厂模式有很大不同。 抽象工厂模式用到了工厂方法模式来创建单一对象,AbstractFactory 中的 cr

  • 工厂方法(Factory Method) Intent 定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化操作推迟到子类。 Class Diagram 在简单工厂中,创建对象的是另一个类,而在工厂方法中,是由子类来创建对象。 下图中,Factory 有一个 doSomething() 方法,这个方法需要用到一个产品对象,这个产品对象由 factoryMethod() 方法创建。