【前端】IOC容器(v4)

什么是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低代码应用平台体验

(0)
nation的头像nation数式员工
上一篇 2023年6月20日 下午4:07
下一篇 2023年11月2日 下午1:58

相关推荐

  • 【路由】浏览器地址栏url参数介绍

    介绍 浏览器地址栏url为路由类型的视图动作(viewAction)的访问url 详情页示例url https://one.oinone.top/page;module=resource;viewType=DETAIL;model=resource.ResourceDistrict;action=redirectDetailPage;scene=redire…

    2024年8月19日
    72400
  • 如何实现页面间的跳转

    介绍 在日常的业务中,我们经常需要在多个模型的页面之间跳转,例如从商品的行可以直接点击链接跳转到类目详情,还有查看该订单发起的售后单列表,这里将给大家展示如何在oinone中如何实现这些功能。 方法一、通过界面设计器的无代码能力配置 表格行跳转到表单页/详情页 拖入一个跳转动作到表格行,保存动作后,在左侧的动作属性面板底部有个请求配置,里面的上下文属性就是配…

    2024年5月13日
    91100
  • oio-button 按钮

    主按钮:用于主行动点,一个操作区域只能有一个主按钮。 默认按钮:用于没有主次之分的一组行动点。 虚线按钮:常用于添加操作。 文本按钮:用于最次级的行动点。 链接按钮:一般用于链接,即导航至某位置。 以及四种状态属性与上面配合使用。 危险:删除/移动/修改权限等危险操作,一般需要二次确认。 禁用:行动点不可用的时候,一般需要文案解释。 加载中:用于异步操作等待…

    2023年12月18日
    15500
  • 列表视图、卡片视图切换

    在日常项目开发中,我们可能会遇到当前视图是个表格,通过某个操作按钮将它变成卡片的形式. 这篇文章将带大家实现这种功能。通过上面两张图片可以看到出来不管是表格还是卡片,它都在当前的视图里面,所以我们需要一个视图容器来包裹表格跟卡片,并且表格可以使用平台默认的表格渲染,我们只需要自定义卡片即可。 我们用资源模块下面的国家菜单来实现这么一个功能这是对应的URL: …

    2024年10月16日
    34401
  • oio-empty-data 空数据状态

    何时使用 当目前没有数据时,用于显式的用户提示。 初始化场景时的引导创建流程。 API 参数 说明 类型 默认值 版本 description 自定义描述内容 string | v-slot – image 设置显示图片,为 string 时表示自定义图片地址 string | v-slot false imageStyle 图片样式 CSSProperti…

    2023年12月18日
    31900

发表回复

登录后才能评论