【问题标题】:Dealing with large classes, how to break them down?处理大类,如何分解?
【发布时间】:2018-03-03 17:29:24
【问题描述】:

进一步研究 OOP 和类,我有一个非常大的用户类,它的管理规模越来越大。总代码超过 950 行,完全摒弃了单一职责原则。

我认为缩小它的几种方法是创建完全独立的类并将数据传输到新类的构造中。例如:

index.php

$user = new user('username');
$userOptions = new userOptions($user);

$user = new user;
$userOptions = new userOptions;

$userOptions->setData(
  $user->getData([
    'username'=>'genericuser'
  ]),
);   

然而,这似乎不自然,也没有我期望的格式正确的代码那么好。

我想到的另一种方法是将基类扩展到另一个基类。例如:

index.php

$userOptions = new userOptions('username');

classes/useroptions.php

class userOptions extends User {
  //a small section of what would be a big user class
}

但这也违反了一些 PHP 实践,extends 通常表示 一种特殊情况,而用户选项似乎不是。

我组织文档的最后一种方式是在默认类中使用将大文件分开,例如:

classes/user.php

class User {

 /**
  * CREATION SECTION
  */
 public function create() {
   //create user
 }



 /**
  * UPDATE SECTION
  */
 public function change($whattochange) {
   //what to change
 }
}

然而,这似乎再次违反了单一职责原则,许多选项都在一个类中。

什么是划分类的常见做法,应该怎么做?

当前的 User 类内部有以下方法:

 * - $variables
 * - __construct gets a new user.
 * - stripeCustomer() gets the customer information if it exists
 * - create($fields|array) method to create a new user
 * - login($username|string, $password|string, $remember|bool) logs in a user
 * - find ($param|string, $method|string) finds a user WHERE param=method
 * - data() returns user data
 * - isLoggedIn() returns whether a user is logged in
 * - isAdmin() returns if a user is an admin
 * - logout() logs out current user
 * - displayName() returns first name or username
 * - isVerified() checks if a user is verified
 * - inCompany() checks if a user is in a company
 * - inVerifiedCompany() checks if a user id in a verified company
 * - verifyUser($apicode|string, $verification|string, $resend|bool) verifies a users email
 * - error() returns any errors that may occur
 * - getProfilePicture($size|int) gets a users profile picture from Gravatar with size of $size in pixels
 * - passwordCheck($password|string) checks if two passwords match
 *
 *   // 2FA section
 * - has2FA() checks if the user has 2FA enabled
 * - TOTP($type|string, $secret|string, $code|string, $backupcodes|array) everything to do with 2FA
 * - countBackups() counts the amount of backup codes remaining for a user with 2FA
 * - update($statement|string, $params|array, $apicode|string) updates a user
 *
 *   // lockdown system
 * - getAttempts() gets amount of attempts to break into a users account
 * - isLocked() gets whether the user account is locked
 * - addAttempt() adds an attempt to a users account
 * - reinstateUser() unlocks a users account
 * - shouldShowCaptcha() checks whether a captcha is needed
 *
 *   // codes
 * - sendRequest($code|int) sends a request to a users email with a specific code
 * - verifyCode($code|string, $type|int) checks a user inputted code to one in the DB
 *
 * - deleteUser() deletes the specific user
 * - makeAdmin() makes the user an admin
 * - removeAdmin() removes the user as an admin
 * - modify($changes|array, $apicode|string) modifies a user | no idea how this is different to update

我也明白该类的数据库部分应该在一个单独的映射器类中,它将使用与我最初尝试相同的样式结构,这将很快改变。

提前感谢所有帮助。

作为参考,我查看了谷歌,发现有些人提出了类似的问题,但似乎没有人在很大程度上回答了这个问题。 How to break up a large class

【问题讨论】:

  • 我喜欢你如何标记这个问题object-oriented-analysis,因为这就是这个问题的意义所在。班级的大小无关紧要;如果你的分析告诉你类需要来做所有这些事情,那么它的大小也是如此。然而,在这种情况下,User 类的责任太多了:它拥有关于自身的知识,但也管理着安全性。您有权承认违反 SRP 规则。您需要做的就是改进您的分析并调整您的实现以匹配设计。将User 类剥离到最低限度,然后从那里开始。

标签: php oop object-oriented-analysis


【解决方案1】:

有一种称为trait 的方法,您可以制作任意数量的特征,然后将其包含在一个类中。制作单独的特征文件,这将使您的代码易于阅读。
trait 实际上就像一个有很多方法的类..
如何使用它 语法将是这样的..

trait UserOptions{

public function create()
{
   // logic goes here
}
public function destroy(){
// logic 
}
}

trait UserAdmin{

  public function isAdmin(){
     return true;

  }
}

class User{

// this is how to include traits
use UserOptions , UserAdmin ;

}

现在所有特征的所有方法都包含在 User 类中。 这就是我们可以分解这么多代码的方式

【讨论】:

    【解决方案2】:

    好吧,我自己并不是真正的专家。但我想这个问题的答案有两个方面。一个不是真正的技术。让我们从它开始吧。

    重构不是一件容易的事。重构遗留代码是两次(我不确定你的代码实际上是遗留代码,但我猜规则成立)。通常由各自领域的专家完成。甚至在他们开始之前,还有几个步骤需要完成。首先,必须记录系统的当前行为。要么使用某种end-to-end (acceptance) tests,要么通过真正彻底的技术文档(由business analytics 完成)。两者都更好。只有这样你才能开始重写过程。没有这个,您根本无法确定系统会像以前一样工作。在最好的情况下,您最终会遇到明显的错误(您可以立即清楚地看到的错误)。但您也可能会遇到一些意想不到的隐含行为,您可能会在一段时间后才注意到。

    关于技术方面。现在你有了所谓的God object。它至少有persistenceauthenticationauthorization。因此,正如您所说,SRP (Single responsibility principle) 完全被驳回。所以首先你必须提取实体(业务对象):

    final class User
    {
        private $id;
        private $name;
        // ...
    
        public function __construct($id, $name)
        {
            $this->id = $id;
            $this->name = $name;
        }
    
        public function id()
        {
            return $this->id;
        }
    
        public function name()
        {
            return $this->name;
        }
    }
    

    然后提取repository 来持久化这个实体:

    final class UserRepository
    {
        /**
         * \PDO or whatever connection you use.
         */
        private $db;
    
        public function __construct(\PDO $db)
        {
            $this->db = $db;
        }
    
        public function findByPk($id): User {}
    
        public function create(User $user) {}
    
        public function update(User $user) {}
    
        public function delete(User $user) {}
    }
    

    将身份验证和授权提取到单独的服务中。更好的是,使用现有的包(你可以在 GitHub 上找到很多)。

    但同样,只有当您有办法确保您的更改没有破坏任何东西时,所有这些都是安全的。

    关于这个主题有一个book。老实说,我自己还没有读过它,但是我和作者一起听了播客,并听到了非常好的评论。所以你可以检查一下。

    【讨论】:

    • 感谢您抽出宝贵时间撰写此答案@sevavitetl。阅读非常有趣,感谢您花时间超链接很多内容,并在您的答案背后提供上下文。在我看来,我一直在寻找 QaMar ALi 提供的“特征”,但我仍然希望感谢您提供的有见地的信息。
    • @MatthewM,我很高兴。请记住,特征的目的是在类之间共享通用功能。所以它基本上是一种multiple inheritances 替代品。因此,当您使用它们来构建您的类时,您只是隐藏了复杂性。
    【解决方案3】:

    您应该考虑通过迁移过程进行重新设计,而不是拼接烂软件。最好的办法是创建一个可以处理旧数据结构但允许引入新(直接)数据结构的新分支。干净利落地也比许多小的迁移步骤要好。两者都存在风险:小步骤需要很长时间,并且每个步骤都可能导致用户沮丧。剪切可能会失败,您可能需要回滚。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-03-24
      • 1970-01-01
      • 2020-08-13
      • 2019-06-20
      • 2019-01-30
      • 1970-01-01
      • 2017-10-25
      相关资源
      最近更新 更多