CRUD和OOD。怎么解决这个问题?

时间:2012-01-11 19:43:40

标签: php mysql oop crud

请务必诚实,如果必须,请将我的工作分开。

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

我的第一个方法是创建类公司,合作伙伴和城市,从数据库中获取所有数据集并从中创建对象:

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个参数)?

  • 您是否应该为数据库设置单独的类 操作或者更好的是有一个抽象类或者 它的接口,让子类本身处理更新,删除,插入?

  • 只要编写,从数据库中删除,只要将这些更改应用到我的对象并且稍后只执行命令到数据库,例如会话结束时,是否常见?

    < / LI>
  • 我认为像这样的应用程序必须在幻想时代完成。这里适当的方法是什么?创建对象,使用对象,将它们保存到数据库?

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

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

非常感谢你的时间。

6 个答案:

答案 0 :(得分:6)

OP提出的问题:

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

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

“你是否应该有一个单独的数据库操作类,或者最好是为它创建一个抽象类或接口,让子类本身处理更新,删除,插入?”

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

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

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

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

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

“只要编写,从数据库中删除,只要或者我应该将这些更改应用于我的对象并且稍后只执行命令到数据库,例如当会话结束时”?

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

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

“我认为像这样的应用程序之前必须完成幻想。这里的正确方法是什么?创建对象,使用对象,将它们保存到数据库中”?

  • 你是对的,这是以前做过的,通常被称为ORM(对象关系映射器)。基本上,ORM会自省数据库模式,并创建表示模式的对象(和关系)。因此,您不使用本机SQL,而是使用对象。虽然您可以使用SQL来满足自定义业务需求,但是对于Doctrine,您可以使用Doctrine查询语言(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();
}

答案 1 :(得分:1)

您实际上是在编写自己的ORM。所以,我不打算只换成已经为你写过的那个。滚动自己的优势在于,您可以在写作时了解它的工作原理。但缺点是其他人可能已经做得更好了。但假设你想继续......

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

具体建议如下:

我会将您的实体实例类设置为容纳数据而不是进行大量数据加载。使用其他类和逻辑来加载数据。我将仅使用实体类的构造函数来填充与该类相关的数据(以及它的子项)。

一件简单的事情是在实体类上使用静态方法来加载和保存数据。 E.g。

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会更好,但这超出了这个答案的范围。我发现在我遇到更简单的解决方案问题之前,我常常看不到像存储库模式这样的更复杂的解决方案的优点。

答案 2 :(得分:1)

  1. 真的很糟糕。在这种情况下,代码是完全不可读的。你有选择
    • 使用setter(可以在里面添加验证逻辑,更好的可读性,不需要用null填充空字段)
    • 为每个域类提供单独的类构建器(为其他对象占用一些内存)。 java中的例子希望你能理解:
      class CompanyBuilder {
      private final Company c;
      public CompanyBuilder() {
       c = new Company();
      } CompanyBuilder addId(String id){c.id = id;} // id should be package visible and class should be located in the same package with builder CompanyBuilder addName(String name){...} CompanyBuilder addCity(String city){...} Company build(){ return c;} }
    • 混合解决方案,有组织链的方法(更糟糕的调试,更好的可读性)。在java中会有方法:
      class Company {
      ...
      Company addId(String id){
      this.id = id;
      return this;
      } Company addName(String name){...} ... } Usage: Company c = new Company().addId("1").addName("Name1");
    • 也许你可以创建更细粒度的对象,以便以后重用它们,并在正确的位置添加特定的逻辑。例如,它可以是公司的地址(位置)对象。
  2. 遵循单一责任原则。 SOLID description on wiki。 在您的情况下,它有助于更​​改特定于数据库的代码,而不会影响系统的其他部分。好吧,单独的域和数据库特定的代码,有共同的接口或抽象类(如果你有所有域类的共同逻辑 - liskov原则)。在子类中实现特定于域的部分。
  3. 如果您不想丢失数据,则应每次保存数据或拥有服务器群集或具有分布式缓存。如果可以丢失,请在会话结束时将它们保存为批处理。它会增加你的表现。如果您有并发更新,也应该每次都保存在事务中。
  4. 方法是从数据或新对象/工作(更新)对象/从对象到数据库写入数据从数据库/构造对象获取数据
  5. 只需编写更多代码并阅读stackoverflow
  6. 最后,我建议阅读“清洁代码:敏捷软件工艺手册”R.Martin。

答案 3 :(得分:0)

你在做什么看起来很棒。您可以添加的是一个中间层,它将您的业务对象映射到您的数据库(对象关系映射)。那里有很多对象关系映射api。查看this维基百科列表,了解可用于PHP的文件

答案 4 :(得分:0)

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

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

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

答案 5 :(得分:0)

您可能需要查看ruby on rails

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