什么是IOC容器?
IOC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合,更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IOC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的使程序的整个体系结构变得非常灵活。在运行期,在外部容器动态的将依赖对象注入组件,当外部容器启动后,外部容器就会初始化。创建并管理对象实例,以及销毁,这种应用本身不负责依赖对象的创建和维护,依赖对象的创建和维护是由外部容器负责的称为控制反转。
IOC(控制反转)和DI(依赖注入)
IOC(Inversion of Control, 控制反转):通过外部容器管理对象实例的一种思想。
DI(Dependency Injection, 依赖注入):IOC的一种实现方式。
作者简述
IOC是Spring框架(一种以Java为语言开发的框架)的核心,并贯穿始终。其面向接口的开发能力,使得服务调用方和服务提供方可以做到完全解耦,只要遵循接口定义的规则进行调用,具体服务的实现可以是多样化的。
对于前端,我们使用inversify
进行了IOC的实现。其强大的解耦能力可以使得平台进行大量的抽象,而无需关系具体的实现。
接下来,我们将介绍IOC在开发中的基本运用。
API
为了方便起见,我们将IOC相关功能与组件SPI的调用方式放在了一起。(更高版本的平台版本将自动获得该能力)
export class SPI {
/**
* register singleton service
*/
public static Service;
/**
* autowired service property/parameter in service
*/
public static Autowired;
/**
* service construct after execute method
*/
public static PostConstruct;
/**
* autowired service in widget
*/
public static Instantiate;
/**
* autowired services in widget
*/
public static Instantiates;
/**
* service construct after execute method in widget
*/
public static InstantiatePostConstruct;
}
创建第一个服务
service/ProductService.ts
import { ServiceIdentifier } from '@kunlun/dependencies';
/**
* 产品
*/
export interface Product {
id: string;
name: string;
}
/**
* 产品服务
*/
export interface ProductService {
/**
* 获取产品列表
*/
getProducts(): Promise<Product[]>;
/**
* 通过ID获取产品
* @param id 产品ID
*/
getProductById(id: string): Promise<Product | undefined>;
}
/**
* 产品服务Token
*/
export const ProductServiceToken = ServiceIdentifier<ProductService>('ProductService');
service/impl/ProductServiceImpl.ts
import { SPI } from '@kunlun/dependencies';
import { Product, ProductService, ProductServiceToken } from '../ProductService';
@SPI.Service(ProductServiceToken)
export class ProductServiceImpl implements ProductService {
public async getProducts(): Promise<Product[]> {
// request api get products
return [];
}
public async getProductById(id: string): Promise<Product | undefined> {
// request api get product by id
return undefined;
}
}
service/impl/index.ts
export * from './ProductServiceImpl';
service/index.ts
export * from './impl';
export * from './ProductService';
这样,我们便创建了一个ProductService服务
,它定义了获取产品列表
和通过ID获取产品
两个方法。接下来,我们要在可能需要的地方进行调用。
在Class Component(ts)
中使用ProductService服务
ProductListWidget.ts
import { BaseElementWidget, SPI, Widget } from '@kunlun/dependencies';
import { ProductService, ProductServiceToken } from '../service';
import ProductList from './ProductList.vue';
@SPI.ClassFactory(BaseElementWidget.Token({ widget: 'Product' }))
export class ProductListWidget extends BaseElementWidget {
@SPI.Instantiate(ProductServiceToken)
protected productService: ProductService | undefined;
public initialize(props) {
super.initialize(props);
this.setComponent(ProductList);
return this;
}
@Widget.Method()
public loadProducts() {
return this.productService?.getProducts();
}
@Widget.Method()
public loadProduct(id: string) {
return this.productService?.getProductById(id);
}
}
在其他Service
实现中使用ProductService服务
OtherServiceImpl.ts
import { SPI } from '@kunlun/dependencies';
import { OtherService, OtherServiceToken } from '../OtherService';
import { ProductService, ProductServiceToken } from '../ProductService';
@SPI.Service(OtherServiceToken)
export class OtherServiceImpl implements OtherService {
@SPI.Autowired(ProductServiceToken)
protected productService!: ProductService;
public async do() {
const products = await this.productService.getProducts();
// do something
}
}
值得一提的是,SPI.Autowired
默认要求服务必须存在,否则会直接报错。如果其不是必须的,可以使用以下方式进行定义:
@SPI.Autowired(ProductServiceToken, { required: false })
protected productService: ProductService | undefined;
其他API的使用
SPI.PostConstruct
在实例初始化后执行,仅用于服务中,无法用于组件中。
@SPI.Service(OtherServiceToken)
export class OtherServiceImpl implements OtherService {
@SPI.Autowired(ProductServiceToken)
protected productService!: ProductService;
@SPI.PostConstruct()
public init() {
console.log('post construct init.', this.productService);
}
}
SPI.Instantiates
与SPI.Instantiate
类似,不过其声明的类型为服务数组,当没有任何服务时,将获得空数组,而不是空。因此可以使用以下方式进行定义:
@SPI.Instantiates(ProductServiceToken)
protected productServices!: ProductService[];
SPI.InstantiatePostConstruct
与SPI.PostConstruct
类似,不同之处在于,其仅用于组件中,无法用于服务中。虽然,我们提供了这种方式,但其实际应用场景相当有限,由于实例化组件时调用的方法为类的constructor方法,其init方法紧跟其后执行,此时,并没有执行任何组件初始化,Vue生命周期等逻辑,因此其对于业务组件来说并不是有意义的。
@SPI.ClassFactory(BaseElementWidget.Token({ widget: 'Product' }))
export class ProductListWidget extends BaseElementWidget {
@SPI.Instantiate(ProductServiceToken)
protected productService: ProductService | undefined;
@SPI.InstantiatePostConstruct()
protected init() {
console.log('post construct init', this.productService);
}
}
SPI.RawInstantiate
该方法并非装饰器,而是可以直接在代码中静态调用的方法。其为SPI.Instantiate
的具体实现,可以在任何地方执行。
const productService = SPI.Instantiate(ProductServiceToken);
SPI.RawInstantiates
与SPI.RawInstantiate
类似,其为SPI.Instantiates
的具体实现,可以在任何地方执行。
const productServices = SPI.Instantiates(ProductServiceToken);
注意事项
- 在使用IOC时,我们并不希望
Class Component(ts)
组件也被注册成服务,因此,在该类上使用SPI.Service
是无效的,甚至可能会带来其他意想不到的问题。 SPI.Autowired
只能用于其他服务,而不能用于Class Component(ts)
组件。SPI.Instantiate
只能用于Class Component(ts)
组件,而不能用于其他服务。- 所有服务实例均为单例,且无法修改。未来可能会提供多例的实现功能,目前从设计角度上考虑不建议使用。
这样设计的目的在于,IOC容器的性能虽然在整体上没有太大的问题,不过为了避免不必要的查找,所以我们并没有通过IOC容器来创建组件。我们需要尽可能的将查找范围最小化。
Oinone社区 作者:nation原创文章,如若转载,请注明出处:https://doc.oinone.top/frontend/52.html
访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验