【问题标题】:Choosing which classes to use in a php OOP design选择在 php OOP 设计中使用哪些类
【发布时间】:2012-01-30 22:54:44
【问题描述】:

我经营一个街机网站,在过去的几年里,它添加了许多功能,到了程序编程看起来太复杂的地步,添加新功能或进行简单的设计修改可能非常棘手。

因此,我决定尝试使用相同的功能但采用 OOP 格式从头开始重新编码该网站。

我遇到的问题是选课,我了解 OOP 以及它应该如何工作,但似乎总是难以入门。我不确定我是否应该尝试为类(例如具有登录用户功能的用户类)创建功能,或者用户类是否应该只是添加/更新/显示用户详细信息并且登录部分在系统中会更好上课?

目前我已经开始使用以下类和函数,但它们是否适合这个类别?

<?
class User {

    var $userId,
        $username,
        $userRole,
        $userEmail;

    function isLoggedIn(){

    }

    function login($postusername, $postpassword)
    {

    }

    function increaseLoginCount(){

    }

    function logout(){

    }
}
?>

然后我可以在 page.php 中包含以下内容 ..(未显示连接类)

<?
$db = new Connect;
$db->connect();

$user = new User;

if(!$user->isLoggedIn())
{
    echo "Please Log In.";

    if($_POST['user'])
    {
        $user->login($_POST['username'], $_POST['password']);
    }
}
else
{
    if($_POST['logout'])
    {
        $user->logout();
        exit;   
    }

    echo $user->username." Logged In.<br />";
}
?>

但是网站会有显示游戏类别的页面,我不知道 displayGames() 函数适合哪里,因为它不是一个单一的游戏,所以不会进入“游戏”类?

我试图找到“真实世界”的示例,但向我展示如何让大象改变颜色或跳舞的 php 代码并没有真正的帮助......

【问题讨论】:

  • 我已经对函数进行了编码,但为了简化这个例子,它们被删除了。

标签: php performance oop procedural


【解决方案1】:

让我们从一些文本分析开始,我强调:

我经营一个街机网站过去几年

想想能应付多少以及你正在处理什么。还要了解,多年来您已经获得了关于运行街机网站特定知识。你已经获得了你所在地区的专业。永远不要低估自己的职位和资产,这是您运营的基础,您将对其进行更改。这包括您网站的用户群。

它添加了许多功能,它已经到了程序编程看起来太复杂并且添加新功能或进行简单的设计修改可能非常棘手的地步。

如果系统增长,它们会变得越来越复杂。这仅特定于过程编程,这是事实。由于您已经运营该网站多年,因此您知道事情发生了怎样的变化,尤其是在用户与您的网站的交互方式方面。

因此,我决定尝试从头开始重新编码网站,使用相同的功能,但采用 OOP 格式。

据说可以使用 OOP 技术来制作可重用的软件,有(而且不可能)任何证据。

但在商业软件开发中,从头开始重新编写整个应用程序取得成功的例子很少。 很少。商业软件开发的规则可能不适用于您的具体情况,所以就这么说吧。

重新编码网站之前请三思。做很多工作只是为了达到同样的目的是有些徒劳的,而且可能会令人失望。相反,您可能更具体地查看您当前的设计中的哪个引入了您希望改变的最大问题。

PHP 可以混合使用过程式和面向对象的风格,当您有遗留代码遗留代码的常见定义是代码没有自动化测试)。

我的问题是选课

我试着换个说法:写课程是为了什么?

了解 OOP 及其工作原理,但似乎总是难以入门

开始总是最艰难的一步。您现在可以做出决定,但您无法展望未来。通过消除风险最大的部分来降低风险。您可能已经开始在这里提出问题以获得一些反馈以作为您的决定的依据,但这可能不会减轻负担并可能导致混乱。然而,接受教育通常是一件好事。

我不确定我是否应该尝试为具有登录用户功能的用户类等类创建函数,或者用户类是否应该只是添加/update/show 用户详细信息和部分登录在系统类中会更好吗?

这在很大程度上取决于您的应用程序的性质和用户的性质。可能您的大部分脚本只需要知道用户是具体用户还是匿名用户(匿名用户尚未登录),它是 ID、姓名或昵称。

应用程序应该向该用户提供任何消费组件,因此每个命令/脚本都不需要处理 a) 获取用户信息和处理用户(如登录),b) 验证组件是否对用户有效(访问控制)。那应该放在其他地方,例如在application controller­PofEAA

每个具有应用程序控制器对象的脚本/命令都可以请求用户并与用户交互。

但这只是技术上的说法。处理自己不确定的事实,处理这些信息。尝试更好地表述你的具体问题,列出解决问题的具体方法的优缺点,在开始编码之前再次远离具体代码。然后比较优劣。尽量让事情变得更简单,不那么复杂。

可能用简单的文字写下应该发生的事情,而不是编写代码。

目前我已经开始使用以下类和函数但它们是否适合这个类别**?**

您的代码非常简单,因此很难说太多 - 特别是因为我不知道您的街机网站是什么(以及您所写的类别是什么)。它可能仍然是一个很好的例子。从您的课程中可以看出,您将所有内容紧密集成在一起。

例如,您从数据库开始。这很常见,因为数据库是任何应用程序的中心组件。应用程序需要数据库才能运行。但是,您希望保持松散耦合,以便您的所有命令都可以与其他数据库或与其他数据库连接的新用户对象一起运行,而不是应用程序的其余数据对象。

 $application->getDB();

由于用户是每个应用程序的中心主体,它应该有一个非常简单的界面。所有关于身份验证、检索用户属性等的荣耀细节都应该委托给另一个类/组件,以便您可以更改存储用户的实现以及他们如何进行身份验证:

 /**
  * @package command.modules.games
  */
 function ListGamesCommand(Request $request, Response $response)
 {
     $application = $request->getApplication();
     $user = $application->getSession()->getUser();
     $games = $application->getModels()->build('games');
     $games = $games->findByUser($user);
     $response->setProp('games', $games);
 }

如本示例所示,您可以在需要时添加该功能。例如。只要您的应用程序不需要实际登录用户,为什么要关心它现在是如何编写的?

创建一个为应用程序对象生成用户的工厂——无论它现在或将来需要什么(参见The Clean Code Talks — Inheritance, Polymorphism, & Testing 中的两堆对象)。如果您随后需要身份验证,请将其添加到会话对象或用户对象接口。

无论如何,身份验证本身将在它自己的一个类中实现,Authenticator。因此,您也可以稍后通过将身份验证调用从会话转移到用户或其他方式来查看您的界面。只有一小部分命令需要处理这些特定任务,并且由于所有新代码都在自动测试中,因为您希望重写并从 OOP 中受益,因此您已确保所有地方都被覆盖并正确重构。

访问请求变量也是如此。如果你想利用 OOP 的好处——它与间接高度相关(并且每一层间接都有代价)——你首先应该让你的基类对特定数据而不是任何数据进行操作(比如全局变量和超全局变量,我在您的示例代码中看到了$_POST)。

因此,让您的新代码能够对请求进行操作并传递响应(输入 - 处理 - 输出):

$request  = new Request($_SERVER, $_GET, $_POST, $_COOKIE, $_FILES, $_ENV);
$response = new Response;

示例取自BankAccount - Sample application used for PHPUnit training

现在下面的一切都可以在RequestResponse 对象上运行 - 处理输入并将其转换为输出。 域命令(执行此操作的脚本/命令)不再需要像使用 $_POST$_GET 那样从 HTTP 请求中提取输入,他们可以直接从Request - 或者如果您编写了自己的一类命令 - 这可以更加量身定制。有些命令可以对自己的请求和响应进行操作。

下一个重要主题是用户界面。你写你想:

我决定尝试从头开始重新编码网站,使用相同的功能但采用 OOP 格式。

我已经写过这样的做法可能是徒劳的。受益于 OOP 代码意味着下次更改代码时,您仍然可以重用组件。随着软件不断变化,这一次现在已经是下一次了。因此,您希望已经重用现有代码。我假设您现有代码的一部分是输出逻辑。所以现有的输出逻辑需要与上面的示例RequestResponse接口。

我打赌你喜欢网站。你喜欢让它们正常工作并且看起来很棒。多年来,您已经建立了自己的网站,即使并非一切都像您希望的那样,您也不想放弃它。因此,对于您的重写至关重要的是,您不会破坏所有内容,但您可以保留它从现在到未来的工作形式(参见 Preserve a Working Application; last point of the list)。

在 webapps 中,视图是至关重要的部分。如果您失去视图,您的网站将失去它的身份。如果您对其进行过多更改,您的网站将会失去现在习惯使用它的用户。

如果你打破它,

  1. 你会注意到吗?
  2. 你能解决它吗?

另一方面,您希望您的应用程序代码(特性、功能)不再与它紧密绑定以便在重写代码时受益。既然你想重写你的应用程序,我们来看看:

     .---------.                            .-------------.
     | website |  ---> [interface in ] ---> | application |
     | user    |  <--- [interface out] <--- |             |
     `---------´                            `-------------´

正如此架构所示,为了使您的应用程序更独立于交互的外观(可以是网站、(智能手机)GUI 或票务系统),应用程序代码应该是可替换的。您不想在新应用程序代码中编写逻辑来获取用户游戏,例如针对每种类型的用户界面,但您在旧应用程序代码中这样做了。

这里以User 对象为例。它的身份验证方式和存储位置不应该是您的新应用程序命令代码关心的问题。如果命令需要它,它就在那里。不是全局的,而是具体的,如果命令要求的话。

因为注册和丢失密码程序是您现有应用程序的一部分并继续存在。

现在您需要将旧代码和新代码放在一起。

因此,您可能会从 HTTP 请求和 HTTP 响应的接口开始。视图从 Interface Out 开始。您通过该接口为视图分配/传递所有需要的数据,您的应用程序不再知道该视图。您无需处理新应用程序代码中的任何 CSS、Javascript 或 HTML 代码。这只是输出的糖。您的应用程序也应该通过控制台/telnet 以纯文本形式或作为远程 XMLRPC 服务、AJAX 端点进行接口 - 随便什么。

因此,您可能只是概括您的视图代码并向其注入变量。编写一个视图层可以像包含一个 PHP 文件一样简单。它对在其范围内可用的变量进行操作。它可以利用其范围内可用的“帮助”函数(模板宏)。它可以利用视图模型对象。甚至可以为视图编写自己的语言(模板语言,一种领域特定语言 (DSL))。

但这只有在您创建一个允许应用程序代码执行此操作的接口时才有可能。

因此,您现在要做的就是将 HTTP/HTML/CSS/JS 从您的应用程序移到它自己的适配器中。该适配器能够制定通用命令,可以通过 interface in 传递给 any 应用程序。

应用程序只会注意执行命令并通过接口输出传递它的响应。所以你现在有两个域:你的应用程序和网站。

您可以开始创建这两个新域,然后为旧代码和新代码提供一个进出接口。

您还有“两个”应用程序并排放置。它们最终与您的数据库绑定在一起(在他们自己的代码中不可见),该数据库负责您网站的数据是否有序。这就是数据库的用途。将数据与您的代码分开,这样您就可以随时更改您的代码。

另外,如果你想重新编码,在现有代码和新代码之间画一个边框。

祝你好运!我希望阅读本文会为您展示一些针对您的具体情况的选项。另请注意,您不会将控制器变成数据库的另一个外观。通过使用轻量级 HTTP 抽象和仅视图层,您可能获得了最大的好处(不知道您的具体最大问题),因为您的应用程序可能仅适用于网站。

因为在 HTTP/PHP 中:

[Interface In]  Plain Text HTTP request
[Application]   Free to go
[Interface Out] Plain Text HTTP response

您通常只需要一些功能来解析输入并构建输出。

此外,不使用胖模型还有一个好处是您可以快速按顺序访问数据,例如如果您不需要一次传递输出(缓冲,一个块),您可以利用将输出流式传输到服务器的好处。

您应该决定哪些部分对您的应用程序是重要的重构,而不是 OOP 与否。与程序一样,OOP 也需要做好。如果您今天在编写过程代码时遇到问题,那么 OOP 代码可能无法解决您的问题。可能需要编写更好的程序代码。只是说,重构一个应用程序并不容易,你应该先找出实际问题。

  1. 如果你打破它,你会注意到吗?
  2. 如果你把它弄坏了,你能修复它吗?

关键部分是您可以注意到并且您手头有一切可以进行修复。

让您的网站处于测试状态,这样您就可以判断是否在此处或那里更改代码实际上做得很好。如果发现它不起作用(更好),则能够将任何更改返回。

完成后,您可以轻松决定是否使用新功能。只要您不需要引入新功能,您编写功能的方式就无需更改任何内容。在此之前,您无法计划新功能。

所以最好在重新编写应用程序之前三思而后行。正如所写,这可能会杀死项目。


另请参阅: How to implement MVC style on my PHP/SQL/HTML/CSS code?­SO Q&A

【讨论】:

    【解决方案2】:

    OOP 就是要确定责任领域并构建独立的代码单元,以处理其中一个领域,并且只处理其中一个领域。一般的经验法则是,系统中的每个对象都应该体现现实世界中的等效对象或概念,但这并不总是正确的,因为您还需要担心使系统工作所需的抽象事物(我的意思是抽象的在这里从某种意义上说,它们不代表业务逻辑项,但仍然需要使系统工作。我不是指抽象类,这完全是另外一回事)。

    例如,在您的游戏网站中,您可能需要处理游戏、用户、版主、帐户、评论、评论等。这些中的每一个都应该是一个独立的类,并且该类的每个实例都应该代表一个特定的用户、游戏、评论等。

    但是类有责任范围,如上所述,一个类应该处理它的责任范围,而不是别的。呈现页面不是上述任何对象类的责任。这就是不代表系统实体的类的来源。您可能需要一个用于 Page 的类、一个用于 Session 的类、一个用于数据库连接的类(尽管 PHP 已经用 PDO 覆盖了那里)以及其他一些数据库模块,例如 mysqli)。

    要呈现页面,您需要使用页面类的实例。您将向它传递对已登录用户对象的引用,对您希望它显示的任何游戏对象的引用等等。然后你让它呈现实际的 HTML。 Page 类不需要了解您传递给它的对象的内部工作原理,除了那些对象公开的 API(在 OOP 圈子中称为对象的协议,换句话说,它们的公共方法和属性)。一个(非常基本的)页面类可能如下所示:

    class Page
    {
        private $user = NULL;
        private $games = array ();
    
        public function setUser (User $user)
        {
            $this -> user = $user;
        }
    
        public function getUser ()
        {
            return ($this -> user);
        }
    
        public function addGame (Game $game)
        {
            $this -> games [] = $game;
        }
    
        public function getGames ()
        {
            return ($this -> games);
        }
    
        public function generate ()
        {
            $user = $this -> getUser ();
            $games = $this -> getGames ();
    
            $pageFile = '/path/to/a/php/script/representing/the/page/markup.php';
            require ($pageFile);
        }
    
        public function __construct (User $user, array $games)
        {
            $this -> setUser ($user);
            foreach ($games as $game)
            {
                $this -> addGame ($game);
            }
        }
    }
    

    实际的 markup.php 脚本可能看起来像这样:

    <html>
        <head>
            <title>Games page for <?php echo ($this -> user -> getName ()); ?>
        </head>
        <body>
        <p>Hello, <?php echo ($this -> user -> getName ()), here are your games.</p>
        <?php if (count ($this -> games)) { ?>
        <ul>
            <?php foreach ($this -> games as $game) { ?>
            <li><?php echo ($game -> getName ()); ?>: your best score is <?php echo ($game -> getHighScore ($this -> user)); ?></li>
            <?php } ?>
        </ul>
        <?php } ?>
        </body>
    </html>
    

    您可能已经注意到,如果您使用这种方法,那么您的应用程序的模块将倾向于属于以下三个类别之一。您的业​​务逻辑对象(如 User 和 Game)、显示逻辑(如 markup.php 文件)以及用作粘合逻辑和协调形式的第三组(如 Page 类)。

    虽然在这种特殊情况下它特定于您的站点,但如果您要进一步推广这种方法,它将落入称为 MVC 的设计模式,它代表模型、视图、控制器(实际上它更接近于称为PAC 代表表示、抽象、控制器,但由于某种原因,它在 PHP 社区中几乎总是被称为 MVC,所以我们现在只说 MVC)。模式是程序员经常遇到的一类问题的概括,因此拥有一个预制解决方案的工具包非常方便。

    在您的游戏应用程序中,User 和 Game 是模型,Page 是控制器,markup.php 是视图。如果您将不同的 markup.php 脚本替换到此代码中,您可以使用它以完全不同的方式呈现完全相同的数据,例如作为 XML 文件。您还可以使用具有不同控制器的相同模型来让它们做不同的事情。这里要记住的重要一点是,模型不应该关心它们是如何被使用的,这样它们就可以根据控制器需要实现的目标以不同的方式使用。

    由于 MVC 是一种模式,因此已经存在用于在 PHP 中构建 MVC(尽管它们不是真正的 MVC ;))应用程序的工具包。这些被称为框架。有很多可供选择,例如 Symfony、CodeIgnitor、Zend Framework 等。现在最流行的框架是 Zend,尽管我个人并不喜欢它。 (我会说 Zend Framework 2 的 beta 版本看起来确实比当前版本的框架好很多)。

    我希望这对您有所帮助。我知道 OOP 一开始可能会让人望而生畏。它确实需要你改变作为程序员的思维方式,但别担心,它会伴随着足够的练习。

    【讨论】:

    • +1 关于 PHP 中的 MVC 的精彩评论。总是让我很恼火,现在它已经成为一个通用术语,它并不真正适合 PHP 范式(当然不是在请求驱动的环境中)。我会质疑 Zend 是否是最流行的框架:google.co.uk/trends?q=zend+framework%2Csymfony
    • 我对受欢迎程度的评论是基于我最近寻找更好工作的经历。每个人似乎都想要 Zend 体验。 :)
    • 很公平。我怀疑任何人都可以自信地说出绝对是最流行的 PHP 框架。从我所在的位置来看,Symfony2 似乎是“唯一”,但我会欣然承认我根本没有深入研究 ZF2。
    • 在检查$_POST[] 值时,您会在编码的标记部分包含该代码吗?
    【解决方案3】:

    用户类中的所有成员看起来都属于那里。将 gui 代码与其他代码分开很重要,我会将 displayGames() 放在某种 gui 类中。

    【讨论】:

      【解决方案4】:

      GordonM 的帖子可以帮助您入门。我当然会建议您使用已建立的框架来帮助您入门 - 它们为您完成了很多繁重的工作,并将帮助您习惯 PHP 中的 OOP。就个人而言,如果您使用的是 PHP5.3,我会推荐 Symfomy2,但这纯粹是个人喜好。我还建议您获取一份the "Gang of Four" book 的副本。这是非常重要的阅读材料,虽然它主要来自非请求驱动的环境背景,但许多模式仍然与 PHP 相关。

      【讨论】:

        猜你喜欢
        • 2011-10-11
        • 1970-01-01
        • 2016-03-02
        • 1970-01-01
        • 1970-01-01
        • 2016-10-29
        • 1970-01-01
        • 1970-01-01
        • 2013-09-28
        相关资源
        最近更新 更多