1. Rely on?

The noun definition and so on is not bothersome, open directly do! Mysql > select * from Mysql where Class B() = new A();

class Mysql{
    public function save(String $data){
        echo('save ['.$data.'] by Mysql'.PHP_EOL); }}class Storage{
    public function save(String $data){
        $Mysql = new Mysql();
        $Mysql->save($data); }}$obj = new Storage();
$obj->save('Tom is smart');//save [Tom is smart] by mysql
Copy the code

1.1 Changed the database

If you are writing a temporary script, this is fine, but if it is a serious project, it will be followed by a subsequent upgrade, iteration, expansion and optimization. For example, we will replace Mysql with PostgreSQL, add PostgreSQL to the code, and modify the Storage class as follows:

class PostgreSQL{
    public function save(String $data){
        echo('save ['.$data.'] by PostgreSQL'.PHP_EOL); }}class Storage{
    public function save(String $data){
        $PostgreSQL = new PostgreSQL();
        $PostgreSQL->save($data); }}$obj = new Storage();
$obj->save('Tom is smart');//save [Tom is smart] by PostgreSQL
Copy the code

1.2 added a caching layer

For example, the traffic increased, add a cache layer to speed up the search, code to add Redis class, and modify the Storage class as follows:

class PostgreSQL{
    public function save(String $data){
        echo('save ['.$data.'] by PostgreSQL'.PHP_EOL); }}class RedisTest{
    public function save(String $data){
        echo('set cache ['.$data.'] by Redis'.PHP_EOL); }}class Storage{
    public function save(String $data){
        $PostgreSQL = new PostgreSQL();
        $PostgreSQL->save($data);
        $Redis      = new $RedisTest;
        $Redis->save($data); }}$obj = new Storage();
$obj->save('Tom is smart');
//save [Tom is smart] by PostgreSQL
//set cache [Tom is smart] by Redis
Copy the code

If you want to change PostgreSQL to Oracle, you need to add Oracle classes and change Storage. For example, to change Redis into MongoDB, the same operation is very troublesome. In fact, the main logic does not care what database is used, what cache, as long as it can be saved, and MY modification should not change the code of the main function, or the corresponding unit test should also be changed? This violates the open and close principle of design patterns and is very troublesome.

2. Inversion of control

Inversion of Control (IoC) is a design principle used in object-oriented programming to reduce coupling between computer code. One of the most common is called Dependency Injection (DI) and another is called Dependency Lookup (WIKIPEDIA)

2.1 Dependency Injection

Dependency injection methods are described in the encyclopedia, but here is only one of the most common examples

class Storage{
    public $db;
    public $cache;
    public function __construct(PostgreSQL $db, RedisTest $cache)
    {
        $this->db    = $db;
        $this->cache = $cache;
    }
    public function save(String $data){
        $this->db->save($data);
        $this->cache->save($data); }}$obj = new Storage(new PostgreSQL(), new RedisTest());
$obj->save('Tom is smart');
$obj->save('Tom is smart');
//save [Tom is smart] by PostgreSQL
//set cache [Tom is smart] by Redis
Copy the code

When we replace db, we do not need to modify the Storage class. However, if we replace DB, the code will still change, such as new PostgreSQL() to new Oracle(), so we will need to instantiate the constructor through new. Or the task instantiated through the factory pattern can be handed to the container.

2.2 Ioc container dependency injection

// Declare a 'DB' interface
interface DB
{
    public function save(String $data);
}
class PostgreSQL implements DB{
    public function save(String $data){
        echo('save ['.$data.'] by PostgreSQL'.PHP_EOL); }}// Declare a 'Cache' interface
interface Cache
{
    public function save(String $data);
}
class RedisTest implements Cache{
    public function save(String $data){
        echo('set cache ['.$data.'] by Redis'.PHP_EOL); }}class Ioc{
    public $instances = [];

    public function register($alias.$class)
    {
        $this->instances[$alias] = $class;
    }
    public function make($alias)
    {
        return $this->instances[$alias]????false; }}class Storage{
    public $db;
    public $cache;
    public function __construct(DB $db, Cache $cache)
    {
        $this->db    = $db;
        $this->cache = $cache;
    }
    public function save(String $data){
        $this->db->save($data);
        $this->cache->save($data); }}$Ioc = new Ioc();
$Ioc->register('db'.new PostgreSQL());
$Ioc->register('cache'.new RedisTest());

$obj = new Storage($Ioc->make('db'), $Ioc->make('cache'));
$obj->save('Tom is smart');
//save [Tom is smart] by PostgreSQL
//set cache [Tom is smart] by Redis
Copy the code

I’m aliasing the class, and the main function is just called, and I don’t care whether it’s Mysql or PostgreSQL, as long as it implements the DB interface.

2.3 Ioc container dependency lookup

With explicit dependency injection, you explicitly inject instantiated classes into the container, whereas with PHP’s reflection mechanism, automatic injection is possible.

// Declare a 'DB' interface
interface DB
{
    public function save(String $data);
}
class PostgreSQL implements DB{
    public function __construct(){}
    public function save(String $data){
        echo('save ['.$data.'] by PostgreSQL'.PHP_EOL); }}// Declare a 'Cache' interface
interface Cache
{
    public function save(String $data);
}
class RedisTest implements Cache{
    public function __construct(){}
    public function save(String $data){
        echo('set cache ['.$data.'] by Redis'.PHP_EOL); }}class Ioc{
    public $instances = [];
    
    public function getInstance($abstract){
        // Get the reflection information of the class, that is, all the information about the class
        $reflector = new \ReflectionClass($abstract);
        // Get constructor information for reflection class
        $constructor = $reflector->getConstructor();
        // Gets the argument to the reflection class's constructor
        $dependencies = $constructor->getParameters();
        if(!$dependencies) {return new $abstract(a); }// A class may have multiple dependent classes
        foreach ($dependencies as $dependency) {if(! is_null($dependency->getClass())){
                $p[] = $this->make($dependency->getClass()->name);
                $p[0] = $p[1]}}// Create a new instance of the class. The arguments given are passed to the class constructor
        // construct the instantiation object of B and push up to the instantiation object of A
        return $reflector->newInstanceArgs($p);
    }
    public function make($abstract){
        return $this->getInstance($abstract); }}class Storage{
    public $db;
    public $cache;
    public function __construct(PostgreSQL $db, RedisTest $cache)
    {
        $this->db    = $db;
        $this->cache = $cache;
    }
    public function save(String $data){
        $this->db->save($data);
        $this->cache->save($data); }}$Ioc = new Ioc();
$obj = $Ioc->make('Storage');
$obj->save('Tom is smart');
//save [Tom is smart] by PostgreSQL
//set cache [Tom is smart] by Redis
Copy the code