耦合原因
比如有以下两个耦合的类型,我们可以看到A类依赖于B类,如果我们要更换B类,则要修改A类的源代码。
class A
{
public $b;
public $num;
function __construct( B $b, int $num ){
$this->b = $b;
$this->num = $num;
}
public function test(){
$this->b->b_test();
}
}
class B
{
public function b_test(){
echo 'this is b_test';
}
}
$b = new B( );
$a = new A( $b );
$a->test();
解除耦合
使用sepbin的注入方式,可以这样解除两个类型之间的耦合,我们稍微更改一下A类
//让A类实现 IFactoryEnable 接口,这个接口规定了两个方法getInstance静态方法和_init()方法,其中getInstance的内容是固定的,它总是返回 Factory::get 获取到的结果。
//为什么不用 Factory::get(A::class) 直接代替 A::getInstance(),实际上是可以这么做的,但在IDE中,使用Factory获取类型单例后,不加上类型注释得不到代码提示,而如果使用A::getInstance(),则可以得到方法提示。sepbin的设计宗旨是对IDE友好
class A implements IFactoryEnable
{
public $b;
public $num;
static public function getInstance(string $config_namespace = null, string $config_file = null, string $config_path = CONFIG_DIR): A {
return Factory::get ( A::class, $config_namespace, $config_file, $config_path );
}
public function _init(FactoryConfig $config) {
//第二个参数表示如果没有b_class这项配置时默认值
$name = $config->getStr('b_class','B');
$this->b = new $name();
$this->num = $config->getInt('num',1);
}
public function test(){
$this->b->b_test();
}
}
class B
{
public function b_test(){
echo 'this is b_test';
}
}
此时,我们可以创建配置文件。配置默认在config目录中。sepbin支持3种格式的配置文件,ini\php\xml。以下示例用php格式写配置如下
//假设在config目录下新建配置文件为 config_a.php
return [
'name_a' => [
'b_class' => 'B',
'num' => 2
]
]
我们用解耦的方法,再次使用A类
$a = A::getInstance( 'name_a','config_a.php' );
$a->test();
我们也可以提前载入配置文件
//提前载入可以在index.php完成,也可以在application类的配置里完成
$config = ConfigUtil::getInstance();
$config->addFile('config_a.php');
$a = A::getInstance('name_a');
$a->test();
多重构造
上例中,B类是没有构造参数的,如果B类也需要构造参数怎么办?比如B类如果是这样:
class B
{
public $b_num;
function __construct( $b_num ){
$this->b_num = $b_num;
}
public function b_test(){
echo 'this is b_test '. $this->b_num;
}
}
我们同样可以按照改动A类的方法改动B类,如下
class B
{
public $b_num;
static public function getInstance(string $config_namespace = null, string $config_file = null, string $config_path = CONFIG_DIR): B {
return Factory::get ( B::class, $config_namespace, $config_file, $config_path );
}
public function _init(FactoryConfig $config) {
$this->b_num = $config->getInt( 'b_num' , 0 );
}
public function b_test(){
echo 'this is b_test '. $this->b_num;
}
}
这时候,A类的_init方法也需要稍作一些改动
class A implements IFactoryEnable
{
public function _init(FactoryConfig $config) {
//第二个参数表示如果没有b_class这项配置时默认值
$name = $config->getStr('b_class','B');
//之前的代码
//$this->b = new $name();
//$this->num = $config->getInt('num',1);
//改动后的代码,$config有一个getClass方法来构造B类
$this->b = $config->getClass( 'b_class', B::class , B::class ); //第一个参数是配置命名,第二个参数是默认值,第三个参数是要检查的类型名称,这里用B的类型去检查,在实际应用中,我们可以规定一个接口去检查
}
}
现在我们可以改动配置来构造A类了,配置文件改动为:
//config.php的内容
return [
'name_a' => [
'b_class' => 'B',
'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
'b_num ' => 1 //这是B类的_init中用到的配置
]
]
]
构造子类时,配置命名会被转换成全小写的下划线命名。如以上B类命名为BoBo,对应的配置应是b_class_bo_bo
用以下代码再次构造A类
$a = A::getInstance( 'name_a' );
$a->test();
由此,我们只需要改变配置文件的值,就可以完成A和B的解耦。另外,我们可以使用这种方法无限级的构造子类。
单例规则
我们如果用同样的配置命名构造的类,总是会获取到类的单例。比如下例:
//变量$a,$b获取到的A类都是同一个单例,意味着$a = $b
$a = A::getInstance('name_a');
$b = A::getInstance('name_a');
在多重构造时,以之前的例子,实际上调用的是
B::getInstance('name_a.b_class_b');
那么如果我们想在构建一个子类时,始终用一个单例,该怎么配置呢?修改配置文件
//config.php的内容
return [
'name_b' => [
b_num => 1
],
'name_a' => [
'b_class' => 'B',
'b_class_b' => 'name_b'
]
'name_a2' => [
'b_class' => 'B',
'b_class_b' => 'name_b'
]
]
那么,我们再来构造A类时,则两个A类都会拥有同一个B类的单例。比如
$a1 = A::getInstance('name_a');
$a2 = A::getInstance('name_b');
//在这个例子中,A有两个实例,但B只有一个实例
//也就是$a1 != $a2 但是 $a1->b = $a2->b