【问题标题】:CRUD and OOD. How to approach this?CRUD 和 OOD。如何解决这个问题?
【发布时间】:2012-01-11 19:43:40
【问题描述】:

请说实话,如果必要的话,请撕毁我的作品。

所以我正在重写我最近制作的一个小型网络应用程序。原因很简单,代码变得非常混乱,我想学习和应用更好的 OO 设计。这个应用程序应该做的只是简单的 CRUD。 我有一个包含 3 个表的数据库,companiespartners 彼此无关,city 与公司和合作伙伴具有 1:n 关系。很简单,真的。现在,我有几个问题将在我的帖子末尾说明。这里我只是尝试解释一下:

我的第一个方法是创建类 company、partner 和 city,从数据库中获取所有数据集并从中创建对象:

class company {

    private $id   = null;
    private $name = null;
    private $city = null;

    //many more attributes

    function __construct( $id, $name, $city, [...] ) {

        $this->id   = $id;
        $this->name = $name;
        $this->city = $city;

        //huge constructor
    }

   /*
    *  getters + setters here
    *
    *  no need to paste the partner class as it looks just like this one
    *
    */
}

这就是所有这些类所做的。我从数据库中获取每个数据集并构建公司、合作伙伴和城市对象(这些类中的属性城市是一个自身具有多个属性的对象)并将它们保存到两个数组arr_companiesarr_partners 中,然后保存这些对象。 ..它像那样工作得很好。

现在,我想要更新、插入、删除数据库,所有 3 个类(城市、公司、合作伙伴)都需要此功能。我的方法是创建一个带有构造函数的新类,该构造函数基本上采用 2 个字符串命令和对象,例如('update', 'company') 然后它将直接在数据库中更新公司,而我的对象保持不变。这让我很伤心,因为我有这么好的构造对象,但我不知道如何使用它们。

问题:

  • 拥有如此庞大的构造函数是不是很糟糕(我最大的构造函数会占用 28 个参数)?

  • 您是否应该有一个单独的数据库类 操作还是最好有一个抽象类或 接口,让子类自己处理更新、删除、插入?

  • 是否经常只在数据库中写入、删除,或者我应该只将这些更改应用于我的对象,然后仅在稍后(例如会话结束时)执行对数据库的命令?

  • 我认为这样的应用程序之前一定已经完成了无数次。这里的正确方法是什么?创建对象、使用对象、将它们保存到数据库中?

  • 我有很多问题,但我想其中有很多我不知道该怎么问。

请注意,如果可能的话,我现在不想使用 ORM。

非常感谢您的宝贵时间。

【问题讨论】:

  • 28 个参数太疯狂了。你可以传入一个数组,或者更好地放弃它并使用 ORM,例如 Idiorm。

标签: php mysql oop crud


【解决方案1】:

OP 中提出的问题:

“拥有如此庞大的构造函数是不是很糟糕(我最大的构造函数需要 28 个参数)”?

  • 是的。想象一下调用代码。您必须传递 28 个不同的值,更不用说每次调用都必须遵守构造函数中指定的确切顺序。如果一个参数不合适,您可能会破坏与参数相关的算法。如果您确实需要传递大量参数,我建议将它们作为数组传递(将example 发布到另一个 SO 问题)。

“你应该有一个单独的数据库操作类,还是最好有一个抽象类或接口,让子类自己处理更新、删除、插入?”

  • 一般来说,在创建类时,您希望尝试识别最能代表您的业务需求的名词。在您的具体情况下,您可能会有三个课程;公司、合作伙伴和城市。

  • 现在在每个类(名词)中,您的方法将采用动词的形式,因此在语义上您的调用代码是有意义的:if ($company->getName() === 'forbes')

  • 正如您所提到的,每个类都需要一个数据库对象 (dbo) 才能使用,因此您可以实现任意数量的模式来向您的类公开数据连接;单例,带工厂的单例,或者依赖注入等。

  • 抽象(父)类非常适合在子类之间共享通用算法,并且应在您处于设计的伪代码阶段时加以识别。父类还允许您通过在父类中声明抽象方法来强制子类具有方法。

  • 接口在某些情况下是一个有用的工具,但我发现它们不如在父类中声明抽象方法灵活。但在班级没有共同的父母的情况下很好。

“是否经常只在数据库中写入、删除,或者我应该只将这些更改应用到我的对象,然后只在稍后(例如会话结束时)执行对数据库的命令”?

  • CRUD 活动应在执行操作时发生。如果您等待会话结束,您可能会遇到会话因用户关闭浏览器而提前结束的情况。为了更好地保护您的数据,您可以将 CRUD 活动包装在事务中。

  • 如果您正在运行一个高流量的应用程序,您可以实现一个排队系统并将要完成的工作排队。

“我认为这样的应用程序之前一定已经完成了无数次。这里的正确方法是什么?创建对象,使用对象,将它们保存到数据库中”?

  • 你是对的,这在以前已经做过了,通常被称为 ORM(对象关系映射器)。基本上,ORM 将内省您的数据库模式,并创建代表您的模式的对象(和关系)。因此,您不是使用原生 SQL,而是使用对象。虽然您可以将 SQL 用于自定义业务需求,但在 Doctrine 的情况下,您将使用 Doctrine Query Language (DQL) 与原生 SQL。

  • 我强烈推荐的 ORM 是 Doctrine

如果您不想使用 ORM,可以将 CRUD 方法添加到主类。我选择了一个接口,因此您的类不必从由数据库操作组成的父级扩展。此外,请查看 this post 了解使用单例/工厂公开您的类数据库对象。

考虑以下几点:

// Company.php
class Company implements iDatabaseOperation

    public function delete()
    {
        // Lets use a DBO singleton/factory for DB access
        //   Uses PDO, which is strongly recommended
        $dbo = Database::factory(Database::DATABASE_NAME);

        $dbo->beginTransaction();

        try {

            $sql = 
                "DELETE FROM " .
                "    company " .
                "WHERE " .
                "    id = :companyId " .
                "LIMIT 1";

            $stmt = $dbo->prepare($sql);

            $stmt->bindValue(':companyId', $this->getId());

            $stmt->execute();

            $dbo->commit();

        } catch (Exception $e) {

            $dbo->rollback();

            error_log($e->getMessage();

            $e = null; // Php's garbage collection sucks
        }
    }
}

// iDatabaseOperation.php
interface iDatabaseOperation
{
    public function delete();
    public function update();
    public function insert();
}

【讨论】:

  • 你太棒了。谢谢你。我一直在考虑使用 Doctrine,但我不确定我是否准备好了。但也许使用它会让事情变得更容易。无论如何,你已经回答了我的大部分问题,我特别喜欢你提供的数组构造函数。仍然困扰我的一件事是我应该如何实现实际的更新、删除、插入逻辑?只需将我指向正确的方向就会有很大帮助。再次感谢。
  • @drwww:没问题。知道是成功的一半。使用代码 sn-p 更新帖子。
【解决方案2】:
  1. 真的很糟糕。在这种情况下,代码完全不可读。你有选择
    • 使用setter(可以在里面添加验证逻辑,更好的可读性,不需要用null填充空字段)
    • 为每个域类拥有单独的类构建器(为附加对象占用一些内存)。 java中的例子希望你能理解: 类 CompanyBuilder { 私人最终公司c; 公共 CompanyBuilder() { c = 新公司();
      } CompanyBuilder addId(String id){c.id = id;} // id 应该是 package 可见的,并且 class 应该与 builder 位于同一个包中 CompanyBuilder addName(String name){...} CompanyBuilder addCity(String city){...} 公司 build(){ return c;} }
    • 具有组织链的方法的混合解决方案(更糟糕的调试,更好的可读性)。在java中将是方法: 类公司{ ... 公司 addId(String id){ 这个.id = id; 返回这个;
      } 公司 addName(String name){...} ... } 用法: 公司 c = new Company().addId("1").addName("Name1");
    • 也许您可以创建更精细的对象以便以后重用它们并在正确的位置添加特定的逻辑。例如,它可以是公司的地址(位置)对象。
  2. 遵循单一职责原则。 SOLID description on wiki。 在您的情况下,它有助于在不影响系统其他部分的情况下更改数据库特定代码。好吧,单独的域和数据库特定代码,具有通用接口或抽象类(如果您对所有域类都有通用逻辑 - liskov 原则)。在子类中实现特定领域的部分。
  3. 如果您不想丢失数据,您应该每次都保存它们或拥有服务器集群或分布式缓存。如果丢失可以在会话结束时将它们保存为批处理。它会提高你的表现。此外,如果您有并发更新,则每次都应保存在事务中。
  4. 方法是从数据库中获取数据/从这些数据中构造对象或新对象/工作(更新)对象/将数据从对象写入数据库
  5. 只需编写更多代码并阅读 stackoverflow

最后我建议阅读“清洁代码:敏捷软件工艺手册”R.Martin。

【讨论】:

    【解决方案3】:

    您实际上是在编写自己的 ORM。所以,我不会打折只是切换到已经为你写的一个。自己滚动的好处是,您可以在编写它时了解它是如何工作的。但缺点是其他人可能已经做得更好了。但假设你想继续……

    一般建议:请记住始终将问题分解为越来越简单的部分。每个类应该只执行一个简单的功能。此外,您不必担心缓存更新...除非您的数据库位于通过调制解调器进行远程连接的另一端。

    具体建议如下:

    我会设置您的实体 instance 类来存放数据而不是进行大量数据加载。使用其他类和逻辑来加载数据。我只会使用实体类的构造函数来填充与该类(及其子类)相关的数据。

    一个简单的做法是在实体类上使用静态方法来加载和保存数据。例如

    class city {
    
        private $id   = null;
        private $name = null;
    
        function __construct( $id, $name ) {
            $this->id   = $id;
            $this->name = $name;
        }
    
        // getters and setters
        ...
    
        // ---------------------
        // static functions
        // ---------------------
    
        public static function loadById($cityId) {
            // pull up the city by id
            $retval = new city(row["id"], row["name"]);
            // close db connection
            return $retval;
        }
    
        public static function loadByCustomerId($customerId) {
            // pull up multiple cities by customer id
            // loop through each row and make a new city object
            // return a hash or array of cities
        }
    
        public static function update($city) {
            // generate your update statement with $city->values
        }
    
        // other methods for inserting and deleting cities
        ...
    }
    

    所以现在获取和更新城市的代码如下所示:

    // loading city data
    $city = city::loadById(1); // returns a city instance
    $cities = city::loadByCustomerId(1); // returns an array of city instances
    
    // updating city data
    $city->name = "Chicago"; // was "chicago"
    city::update($city); // saves the change we made to $city
    

    静态方法不是实现这一点的最佳方式,但它可以让您找到正确的方向。 repository pattern 会更好,但这超出了这个答案的范围。我发现,在我遇到更简单的解决方案的问题之前,我常常看不到像存储库模式这样更复杂的解决方案的优点。

    【讨论】:

      【解决方案4】:

      你所做的看起来很棒。您可以添加一个中间层,它将您的业务对象映射到您的数据库(对象关系映射)。有很多对象关系映射 api。检查this wikipedia 列表中可以用于 PHP 的列表

      【讨论】:

      • "请注意,如果可能的话,我现在不想使用 ORM。"
      • 您要构建的是对象关系映射。为什么要重新发明轮子?如果您不想使用它们,可以使用它们来指导您必须做什么
      【解决方案5】:

      我认为具有 28 个参数的构造函数太多了,您应该让其他类管理一些具有一些共同点的属性。你应该告诉我们你实例化了什么样的其他属性,它可以帮助你找到一种方法来制作更常见的对象。

      我认为您还应该创建一个类来管理操作和数据库,例如具有删除、更新等功能的 DBHandler。 在我看来,在调用函数后直接对数据库中的元组进行修改很重要。

      为什么?因为它可以避免冲突,例如您尝试更新应该被删除的对象,例如,如果您最后对数据库进行了修改。

      【讨论】:

      • 您的最后一点很有道理,谢谢。您能否详细说明“您是否应该让其他类管理具有某些共同点的某些属性”。 ?你这是什么意思?
      • 这取决于你在构造函数中实例化的属性,你能给我们一个pastebin或者你的构造函数里面的东西吗?
      • 好吧,我不懂德语,但据我所知,使用这些属性 $tel_num_ges, $fax, $tel_num_auto, $tel_num_priv 您可以创建一个对象 PhoneAccess() 例如,它会让你的构造函数更轻,但这不是强制性的..
      【解决方案6】:

      你可能想看看ruby on rails

      你不一定要切换到它,但看看他们如何实现 MVC 模式并实现 CRUD。

      【讨论】:

        猜你喜欢
        • 2012-04-05
        • 2014-10-07
        • 1970-01-01
        • 1970-01-01
        • 2019-04-20
        • 2011-06-07
        • 1970-01-01
        相关资源
        最近更新 更多