处理大 类,如何分解它们?
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。老实说,我自己还没有读过,但是我和作者一起听了播客,听到了非常好的评论。所以你可以看看。
进一步研究 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。老实说,我自己还没有读过,但是我和作者一起听了播客,听到了非常好的评论。所以你可以看看。