何时使用特征或服务?

When to use a Trait or a Service?

对于这个例子,我需要通过一些 AuthService 发送电子邮件,而不是其他人(FacebookAuthServiceGoogleAuthServiceLoginFormAuthService 应该发送电子邮件,但是ApiAuthService 不应该,所以我不能使用 AbstractAuth parent class 这样做)。

类 在这里是相关的,因为它们都是关于 Auth 的,但这只是一个例子。不相关的 class 怎么办(一个用于 Auth,一个用于上传等)?

鉴于这些 classes:

class MailerService() { /* do stuff to send emails */}

class FacebookAuthService {
    public function connect() {}
}

class GoogleAuthService {
    public function connect() {}
}

class LoginFormAuthService {
    public function connect() {}
}

class ApiAuthService {
    public function connect() {}
}

这样做是否更有效率(使用 LoginMailService):

class LoginMailService() {
    public function send(User $user, MailerService $mailer) {
        $mailer->sendTo($user->email());
        $mailer->message('Login email message');
    }
}

class FacebookAuthService {
    public function connect(User $user, LoginMailService $loginMail) {
        $loginMail->send($user->email());
    }
}

class GoogleAuthService {
    public function connect(User $user, LoginMailService $loginMail) {
        $loginMail->send($user->email());
    }
}

class LoginFormAuthService {
    public function connect(User $user, LoginMailService $loginMail) {
        $loginMail->send($user->email());
    }
}

class ApiAuthService {
    public function connect(User $user) {}
}

或这样做(具有特征):

trait SendLoginMailTrait {
    private function sendLoginMail(User $user, MailerService $mailer) {
        $mailer->sendTo($user->email());
        $mailer->message('Login email message');
    }
}

class FacebookAuthService {
    use SendLoginMailTrait;

    public function connect(User $user) {
        $this->sendLoginMail($user->email());
    }
}

class GoogleAuthService {
    use SendLoginMailTrait;

    public function connect(User $user) {
        $this->sendLoginMail($user->email());
    }
}

class LoginFormAuthService {
    use SendLoginMailTrait;

    public function connect(User $user) {
        $this->sendLoginMail($user->email());
    }
}

class ApiAuthService {
    public function connect(User $user) {}
}

概念是正交的,特征和服务依赖性不可互换。

您建议在问题中使用特征的方式是错误的,破坏了封装并使维护更加困难。

这是依赖注入的明显案例。

为您的MailerService创建一个界面:

interface MailerServiceInterface {
    public function message($to, $body);
}
class MailerService implements MailerServiceInterface { 
    public function message($to, body) {
        /* message implementation */
    }
}

并且您的不同服务应依赖于该接口:

class FacebookAuthService
{
    private MailerServiceInterface $mailer;

    public __construct(MailerServiceInterface $mailer) {
        $this->mailer = $mailer;
    }
    public function connect(User $user) {
        $this->mailer->message($user->getEmail(), 'Login Message');
    }
}

现在代码已解耦,您的 "auth service" 只依赖于一个可以有多个实现的通用接口,如果您创建一个新的实现,您可以简单地 "inject" 新的实现而不必触摸多个 "auth services" 类.

中的任何内容

组合这些功能不仅在概念上是错误的(发送邮件并不是您真正的身份验证服务职责,这些 类 应该不知道这些通知的实际发送方式),而且在实践中也存在问题:

在现实生活中,您的邮件程序服务也将拥有自己的依赖项(例如,与外部通信的 HTTP 客户端 API、访问某些配置以了解发送消息时使用的凭据, ETC)。有了 traits,你将无法声明这些依赖关系,并且如果不在每次函数调用时注入它们,就无法满足它们。

关于“何时使用特征以及何时使用服务?”的一般性问题没有多大意义,因为正如我在开头所说的那样,这些概念是正交的。最好表述为“何时使用特征?”。答案是:很少.

但是,如果您想要一个更通用的答案以及特征的有效用例,我会向您指出 this good post 和一些很好的例子。