php:在sepbin中实现的另类依赖注入

耦合原因

比如有以下两个耦合的类型,我们可以看到A类依赖于B类,如果我们要更换B类,则要修改A类的源代码。

 
  1. class A
  2. {
  3. public $b;
  4. public $num;
  5. function __construct( B $b, int $num ){
  6. $this->b = $b;
  7. $this->num = $num;
  8. }
  9. public function test(){
  10. $this->b->b_test();
  11. }
  12. }
  13. class B
  14. {
  15. public function b_test(){
  16. echo 'this is b_test';
  17. }
  18. }
  19. $b = new B( );
  20. $a = new A( $b );
  21. $a->test();

解除耦合

使用sepbin的注入方式,可以这样解除两个类型之间的耦合,我们稍微更改一下A类

 
  1. //让A类实现 IFactoryEnable 接口,这个接口规定了两个方法getInstance静态方法和_init()方法,其中getInstance的内容是固定的,它总是返回 Factory::get 获取到的结果。
  2. //为什么不用 Factory::get(A::class) 直接代替 A::getInstance(),实际上是可以这么做的,但在IDE中,使用Factory获取类型单例后,不加上类型注释得不到代码提示,而如果使用A::getInstance(),则可以得到方法提示。sepbin的设计宗旨是对IDE友好
  3. class A implements IFactoryEnable
  4. {
  5. public $b;
  6. public $num;
  7. static public function getInstance(string $config_namespace = null, string $config_file = null, string $config_path = CONFIG_DIR): A {
  8. return Factory::get ( A::class, $config_namespace, $config_file, $config_path );
  9. }
  10. public function _init(FactoryConfig $config) {
  11. //第二个参数表示如果没有b_class这项配置时默认值
  12. $name = $config->getStr('b_class','B');
  13. $this->b = new $name();
  14. $this->num = $config->getInt('num',1);
  15. }
  16. public function test(){
  17. $this->b->b_test();
  18. }
  19. }
  20. class B
  21. {
  22. public function b_test(){
  23. echo 'this is b_test';
  24. }
  25. }

此时,我们可以创建配置文件。配置默认在config目录中。sepbin支持3种格式的配置文件,ini\php\xml。以下示例用php格式写配置如下

 
  1. //假设在config目录下新建配置文件为 config_a.php
  2. return [
  3. 'name_a' => [
  4. 'b_class' => 'B',
  5. 'num' => 2
  6. ]
  7. ]

我们用解耦的方法,再次使用A类

 
  1. $a = A::getInstance( 'name_a','config_a.php' );
  2. $a->test();

我们也可以提前载入配置文件

 
  1. //提前载入可以在index.php完成,也可以在application类的配置里完成
  2. $config = ConfigUtil::getInstance();
  3. $config->addFile('config_a.php');
  4. $a = A::getInstance('name_a');
  5. $a->test();

多重构造

上例中,B类是没有构造参数的,如果B类也需要构造参数怎么办?比如B类如果是这样:

 
  1. class B
  2. {
  3. public $b_num;
  4. function __construct( $b_num ){
  5. $this->b_num = $b_num;
  6. }
  7. public function b_test(){
  8. echo 'this is b_test '. $this->b_num;
  9. }
  10. }

我们同样可以按照改动A类的方法改动B类,如下

 
  1. class B
  2. {
  3. public $b_num;
  4. static public function getInstance(string $config_namespace = null, string $config_file = null, string $config_path = CONFIG_DIR): B {
  5. return Factory::get ( B::class, $config_namespace, $config_file, $config_path );
  6. }
  7. public function _init(FactoryConfig $config) {
  8. $this->b_num = $config->getInt( 'b_num' , 0 );
  9. }
  10. public function b_test(){
  11. echo 'this is b_test '. $this->b_num;
  12. }
  13. }

这时候,A类的_init方法也需要稍作一些改动

 
  1. class A implements IFactoryEnable
  2. {
  3. public function _init(FactoryConfig $config) {
  4. //第二个参数表示如果没有b_class这项配置时默认值
  5. $name = $config->getStr('b_class','B');
  6. //之前的代码
  7. //$this->b = new $name();
  8. //$this->num = $config->getInt('num',1);
  9. //改动后的代码,$config有一个getClass方法来构造B类
  10. $this->b = $config->getClass( 'b_class', B::class , B::class ); //第一个参数是配置命名,第二个参数是默认值,第三个参数是要检查的类型名称,这里用B的类型去检查,在实际应用中,我们可以规定一个接口去检查
  11. }
  12. }

现在我们可以改动配置来构造A类了,配置文件改动为:

 
  1. //config.php的内容
  2. return [
  3. 'name_a' => [
  4. 'b_class' => 'B',
  5. 'b_class_b' => [ //由于我们在$config->getInstance的第一个参数设置的前缀b_class,因此这里用b_class作为前缀构造B, _b代表B的类名,必须全部小写。假设我们还有继承至B的,B1,B2方法,这里的配置名应写成 b_class_b1,b_class_b2,且如果有命名空间请忽略,如\SepLib\B,则任然应写成 b_class_b
  6. 'b_num ' => 1 //这是B类的_init中用到的配置
  7. ]
  8. ]
  9. ]

构造子类时,配置命名会被转换成全小写的下划线命名。如以上B类命名为BoBo,对应的配置应是b_class_bo_bo

用以下代码再次构造A类

 
  1. $a = A::getInstance( 'name_a' );
  2. $a->test();

由此,我们只需要改变配置文件的值,就可以完成A和B的解耦。另外,我们可以使用这种方法无限级的构造子类。

单例规则

我们如果用同样的配置命名构造的类,总是会获取到类的单例。比如下例:

 
  1. //变量$a,$b获取到的A类都是同一个单例,意味着$a = $b
  2. $a = A::getInstance('name_a');
  3. $b = A::getInstance('name_a');

在多重构造时,以之前的例子,实际上调用的是

 
  1. B::getInstance('name_a.b_class_b');

那么如果我们想在构建一个子类时,始终用一个单例,该怎么配置呢?修改配置文件

 
  1. //config.php的内容
  2. return [
  3. 'name_b' => [
  4. b_num => 1
  5. ],
  6. 'name_a' => [
  7. 'b_class' => 'B',
  8. 'b_class_b' => 'name_b'
  9. ]
  10. 'name_a2' => [
  11. 'b_class' => 'B',
  12. 'b_class_b' => 'name_b'
  13. ]
  14. ]

那么,我们再来构造A类时,则两个A类都会拥有同一个B类的单例。比如

 
  1. $a1 = A::getInstance('name_a');
  2. $a2 = A::getInstance('name_b');
  3. //在这个例子中,A有两个实例,但B只有一个实例
  4. //也就是$a1 != $a2 但是 $a1->b = $a2->b
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值