【问题标题】:OOP efficiency when using a class in another class在另一个类中使用一个类时的 OOP 效率
【发布时间】:2013-05-17 09:44:47
【问题描述】:

我有一个名为 DB (class.pdo.php) 的类,它使用 PDO 处理 mysql 查询的所有处理,还有一个名为 user 的类,我用来管理登录系统。

我的问题涉及总是必须在用户的每个公共函数中实例化 $db,以便我可以使用 DB。这有效率吗?我不应该在用户的 __construct() 中实例化数据库吗?

这是我的代码

 require_once("../../class.pdo.php");

class user {

private $db = null;

public function __construct(){
    /* Empty? */
}

public function find_by_email($email){
    $db = new db();
    $db->query('SELECT * FROM users WHERE email = :email LIMIT 1');
    $db->bind(':email',$email);
    $result = $db->single();
    return $result;
}

public function create($email,$password,$first_name,$last_name){ 
    $db = new db();
    $db->query("INSERT INTO users(email,password,first_name,last_name,created_at) VALUES (:email,:password,:first_name,:last_name,NOW())");
    $db->bind(':email',$email);
    $db->bind(':password',$password);
    $db->bind(':first_name',$first_name);
    $db->bind(':last_name',$last_name);
    $result = $db->execute();
    return $db->lastInsertId();
}

 [more similar functions ommited]

【问题讨论】:

  • 你如何定义“高效”这个词?你如何衡量“效率”?
  • 实例化一次,并使用依赖注入将其传递给所有需要它的类
  • 你的班级和PDO有什么区别?
  • 这很可能是低效的。从软件工程的角度来看,这也不好:你的类(它的方法)依赖于db 的存在,但不要在任何地方做广告;它只有在您阅读方法实现时才可见。
  • @Afonso Gomes:服务器提供和使用的资源有几十种。而且您无法优化所有这些的使用。资源 A 的每次优化都会消耗一些单位的资源 B。

标签: php class oop pdo


【解决方案1】:

好吧,尽管一些 cmets 建议使用单例模式,但我完全不同意将其用于此目的。

您的应用程序不会总是使用与一个数据库的单一连接。

让我告诉你我会怎么做:

class DbConnector {
    private $dbh;
    private $dsn;

    public function __construct($dsn) {
        $this->dsn = $dsn;
    }

    private function connect() {
        if($this->dbh === null) {
            $this->dbh = new PDO($this->dsn);
        }
    }

    public function disconnect {
        if($this->dbh !== null) {
            $this->dbh = null;
        }
    }

    public function query($sql) {
        $this->connect();
        //... do the rest
    }

    public function fetchAll($sql) {
        $this->connect();
        //... do the rest
    }

    public function insert($table, $values) {
        $this->connect();
        //... do the rest
    }

    public function update($table, $values, $cond) {
        $this->connect();
        //... do the rest
    }

    public function delete($table, $cond) {
        $this->connect();
        //... do the rest
    }
}

class User {
    private $dbConn;
    public function __construct(DbConnector $dbConn) {
        $this->dbConn = $dbConn;
    }

    public function create($email,$password,$first_name,$last_name){ 
        $this->dbConn->query("INSERT INTO users(email,password,first_name,last_name,created_at VALUES (:email,:password,:first_name,:last_name,NOW())");
        $this->dbConn->bind(':email',$email);
        $this->dbConn->bind(':password',$email);
        $this->dbConn->bind(':first_name',$email);
        $this->dbConn->bind(':last_name',$email);
        $this->dbConn->execute();
        return $this->dbConn->lastInsertId();
    }

    // ...
}

结果:

  • 未使用单例 = 可测试。
  • 需要时才打开与数据库的连接
  • 您的连接是持久的。如果您在每种方法中打开和关闭连接,您将失去创建事务的能力。

【讨论】:

    【解决方案2】:

    如何使用单例模式为连接创建一个对象并在每次需要时使用它,而不是一直创建新对象?

    【讨论】:

    • Singleton 与其说是一种模式,不如说是一种反模式。数据库连接器不应该使用单例,因为并非总是只有一个连接到一个数据库。
    • @HenriqueBarcelos 是什么阻止了您在默认连接中使用单例,但在提供不同凭据时创建另一个实例?
    • 如果你不注入单例,你不能改变它。如果您只是这样做:public function someMethod() { DbConnector::getInstance()->query(...); } 您将永远无法更改连接器。
    • @HenriqueBarcelos 所以,它本身不是单例,而是实现。不过无论如何,你总是可以做类似DbConnector::getInstance('another')->query(...) 的事情。一切皆有可能。
    • 嗯,这是一个(单例)注册表,不再是单例了。它使事情变得更好一些。我必须详细说明一下为什么使用全局状态模式(例如 Singleton 和 Registry)不好,但它是一个有效的解决方案。
    【解决方案3】:

    我会用延迟加载做类似的事情:不要在构造函数中启动,除非你确定每次创建对象时确实需要连接,但绝对不要在每次方法调用时创建新对象。相反,将生成的对象保存到一个对象 var 中,在每次方法调用时都会检查该对象 var,如果丢失则启动连接。

    class user {
      protected $_db = null;
      private function _init_db() { $this->_db = new XXX; }
      public function create( $x, $y, $z ) {
        if ( ! $this->_db ) $this->_init_db();
        # use $this->_db ..
      }
      public function find_by_email( $x, $y, $z ) {
        if ( ! $this->_db ) $this->_init_db();
        # etc
      }
    }
    

    这具有避免全局静态状态(单例..)的优点,并且仅在最后一刻创建连接/对象,因此您确定确实需要它,而不仅仅是无用的连接。

    【讨论】:

      【解决方案4】:

      说到效率,您的代码的主要问题是它为每个调用的方法建立新连接。这确实效率低到会杀死您的数据库服务器。这与您遇到的其他问题无法比拟。

      因此,一般而言,您可以采用任何您想要的方式 - 在每个函数中以某种方式获取 db 类的实例或使用类变量 - 但无论哪种方式都必须在整个应用程序中使用 单个 PDO 实例。

      另外,从代码量的角度来看,我发现你的函数效率很低,并且会以这种方式优化它们

      public function create($email,$password,$first_name,$last_name){ 
          $sql = "INSERT INTO users(email,password,first_name,last_name,created_at) VALUES (?,?,?,?,NOW())";
          $this->db->query($sql);
          $result = $db->execute(func_get_args());
          return $db->lastInsertId();
      }
      

      【讨论】:

        【解决方案5】:

        从对象的角度来看,我会让数据库在方法中进行实例化,而不是在整个类中进行。

        每个方法应该只看到它需要的变量和数据,以便执行它的功能。 例如,createUser() 方法需要查看变量或属性,例如 $username$usergroupId 以及 $database 等。

        但是,您可能有一个名为randomPassword() 的函数,它会根据数字和字母生成随机密码。

        这个 randomPassword() 函数不需要数据库对象,因此,在对象范围内已经初始化的数据库连接将是浪费的。

        最好只在需要它的方法中创建新的数据库对象。

        另外,在我的应用程序中,我不会在每次使用new database 时创建新的数据库连接。相反,我选择了保持连接处于活动状态的单例 PDO 数据库对象。

        然后我可以静态调用数据库对象来检索现有连接。因此,如果在运行我的应用程序的过程中我需要有 20 个数据库对象,那么我的应用程序只返回相同的对象和相同的连接。

        【讨论】:

        • 我不同意为每个方法创建一个连接,因为这样数据库中的信息没有持久性。连接器应该在之前创建并传递给对象。每个方法都可以通过调用 DB 类中的 connect 之类的方法来负责,因为在创建 DB 对象时不需要立即连接。这称为延迟初始化。
        • 我同意@henriques Barcelos,我确实补充说,我没有为每个方法创建一个新对象,而是我个人有一个单例 PDO,我只需将它导入到每个需要它的方法中:)
        • 所以......你是说我走在正确的轨道上?我在类 db 的 __construct 中有这个 try{$this->dbh = new PDO($dsn, $this->user, $this->pass, $options);}
        • 单例也是个问题。如何使用单例对这些类进行单元测试?
        • 单例可能会导致单元测试出现问题,但是每次应用程序运行打开 10 个连接或在适当的时候打开 1 个可重用连接是否更有效?为什么不合并父工厂模式以在正常请求时返回单例对象,但在需要时返回新连接?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-08-26
        • 1970-01-01
        • 2017-09-02
        • 2016-04-17
        • 2014-10-06
        • 2013-02-17
        相关资源
        最近更新 更多