在给定条件的情况下,使用子类的实例而不是父类



我正致力于将一个大型的、单片的类分解为几个子类,但一次完成太多了,所以我希望在时间允许的情况下,将它们一个接一个地分解为几个版本。这是一个授权某些通道的身份验证类,所以目前它看起来是这样的:

$auth = new Auth($user, $data);
$output = $auth->authChannel($channelName);

Auth内部,它基本上看起来是这样的:

public function __construct($user, $data)
{
$this->user = $user;
$this->data = $data;
}
public function authChannel($channel)
{
$this->setUserData();
if (isset(self::CHANNEL_AUTH_FUNCTIONS[$channel])) {
$authFunction = self::CHANNEL_AUTH_FUNCTIONS[$channel];
return $this->$authFunction();
} else {
// invalid channel
}
}

所以self::CHANNEL_AUTH_FUNCTIONS基本上就是['channelA' => 'authChannelA', 'channelB' => 'authChannelB'],等等,所有这些函数都在这个类中。

现在我想做的是if $legacyChannel => callLegacyFunction() / else $newChannel => instantiate its own class and call auth(),一次一个。

因此,我将Auth.php放入它自己的名称空间,并在同一名称空间中拥有新的Channel.php类。和CCD_ 7。

目前我有这个:

public function authChannel($channel)
{
$this->setUserData();
if (isset(self::CHANNEL_AUTH_LEGACY_FUNCTIONS[$channel])) {
$authFunction = self::CHANNEL_AUTH_LEGACY_FUNCTIONS[$channel];
if ($authFunction) {
return $this->$authFunction();
} else {
$authClassName = __NAMESPACE__ . '\' . ucwords($channel);
$authClass = new $authClassName($user, $data);
return $authClass->auth();
}
} else {
// invalid channel
}
}

有更好的方法吗?目前,这似乎有点浪费,因为创建了两个不同的对象,例如setUserData()函数,我认为需要再次调用。我还想知道是否有比__NAMESPACE__ . / . $className更好的方法来获得动态类名。

在代码看起来更好之前,您必须进行大量工作。我将尝试建议尽可能少的更改,以使"迁移"尽可能无痛,尽管您离干净的设计只有几步之遥。

首先,您可以为新的身份验证类创建一个AuthStrategyInterface

interface AuthStrategyInterface
{
public function supports(string $channel): bool;
public function auth($user, $data);
}

每个新的身份验证类都应该实现这个接口。方法supports($channel)很容易理解:如果一个身份验证类可以处理某个通道,那么它应该返回true。

您的Auth类需要一种方法来注入这些策略。通常你会在构造函数中这样做。。。但为了保持API不变,我们只创建一个setter方法。

当执行authChannel()时,它将首先检查注入的策略,看看是否有任何策略支持使用的$channel,并在可能的情况下使用它。如果没有,请返回检查旧的实现。

这样,在添加新的身份验证策略时,就不需要接触任何旧代码。当您添加新的实现时,您正在逐渐扼杀遗留系统。在某一点上,没有使用旧的实现,您可以进入新的代码重构阶段。

class Auth {
private iterable $strategies = [];
public function __construct($user, $data)
{
$this->user = $user;
$this->data = $data;
}
public function setAuthStrategies(iterable $strategies)
{
$this->strategies = $strategies;
}

public function authChannel($channel)
{
$this->setUserData();
// check if any of the new strategies supports  
foreach ($this->strategies as $strategy) {
if ($strategy->supports($channel) {
return $strategy->auth($this->user, $this->data);
}
}
// check "legacy" authentication methods.
if (isset(self::CHANNEL_AUTH_FUNCTIONS[$channel])) {
$authFunction = self::CHANNEL_AUTH_FUNCTIONS[$channel];
return $this->$authFunction($this->user, $this->data);
}
// no valid authentication method
return false;     
}
}

要使用它,你可以这样做:

$fooAuthStrategy = new FooAuthStrategy();
$barAuthStrategy = new BarAuthStrategy();
$bazAuthStrategy = new BazAuthStrategy();
$auth = new Auth($user, $data);
$auth->setAuthStrategies(
[
$fooAuthStrategy,
$barAuthStrategy,
bazAuthStrategy
]
);
$auth->authChannel($channel);

具体情况会根据你的应用程序的设置方式而变化,但这样的做法会比你目前的方法更进一步。

我不知道我是否正确理解了这个问题,但你不能这样做吗?

public function authChannel($channel)
{
$this->setUserData();
if (!isset(self::CHANNEL_AUTH_LEGACY_FUNCTIONS[$channel])) {
// Invalid channel
return;
}
return  self::CHANNEL_AUTH_LEGACY_FUNCTIONS[$channel]
? $this->$authFunction()
: parent::auth();
}

最新更新