处理大 类,如何分解它们?

Dealing with large classes, how to break them down?

进一步研究 OOP 和 classes,我有一个非常大的用户 class,管理起来越来越大。总代码超过 950 行,完全违背了单一职责原则。

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

index.php

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

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

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

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

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

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 通常意味着 的特殊情况 用户选项似乎不是。

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

classes/user.php

class User {

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



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

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

分离一个 class 的常见做法是什么?应该如何做?

当前用户class里面有以下方法:

 * - $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

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

在此先感谢您的帮助。

作为参考,我查看了 google 并发现一些人在问类似的问题,但是 none 似乎在很大程度上回答了这个问题。 How to break up a large class

有一种方法叫做 trait 你可以制作任意数量的特征,然后包含在 class.. 制作单独的特征文件,这将使你的代码易于阅读。 .
trait 实际上就像一个有很多方法的 class。
使用方法 语法将是这样的..

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 ;

}

现在所有特征的所有方法都包含在用户 class 中。 这就是我们如何分解这么多代码

与其拼接烂软件,不如考虑 re-design 带有迁移过程的软件。最好的办法是创建一个新的分支,它可以处理旧的数据结构,但允许引入新的(直接的)数据结构。干净利落地也比许多小的迁移步骤要好。两者都存在风险:小步骤需要很长时间,每一步都会导致用户感到沮丧。剪切可能会失败,您可能需要回滚。

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

重构不是一项微不足道的任务。重构遗留代码是两次(我不确定您的代码实际上是遗留代码,但我想规则成立)。通常由各自领域的专家完成。甚至在他们开始之前,还有几个步骤需要完成。首先,必须记录系统的当前行为。要么使用某种 end-to-end (acceptance) tests or by really thorough technical documentation (done by business analytics)。两者都更好。只有这样你才能开始重写的过程。否则,您根本无法确定系统会像以前一样工作。在最好的情况下,您最终会遇到明显的错误(您可以立即清楚地看到的错误)。但是你也可能会以一些意想不到的隐式行为结束,你可能只有在一段时间后才会注意到。

关于技术方面。现在你已经 so-called God object. It does at least persistence, authentication and authorization. So as you stated 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。老实说,我自己还没有读过,但是我和作者一起听了播客,听到了非常好的评论。所以你可以看看。