4

我正在寻找一种在 PHP 5.x 中使用静态类实现适配器模式的好方法。

我想使用它的示例之一是与 Python 的os.path.join(). 我将有两个适应者,一个 Windows 和一个 Linux 适应者类。

我认为将这些类实现为静态类是合理的,因为它们没有“上下文”。他们不需要存储任何状态,并且每次我需要一个实例时都创建一个实例似乎是多余的——因此我正在寻找一种干净的方法来实现它。

让我们考虑以下虚假实现:

    static public function join(){
        $parts = func_get_args();
        $joined = array(MY_MAGICALLY_PASSED_DEFAULT_PATH_PREFIX);
        foreach($parts as $part){
            $part = self::$adaptee->cleanPath($path);
            if(self::$adaptee->isAbsolute($part)){
                $joined = array($part);
            }
            else{
                $joined[] = $part;
            }
        }
        return implode(PATH_SEPARATOR, $joined);
    }

您会注意到的第一件事是,它假定了一个名为adaptee 的已初始化静态成员,该成员将保存必要的、依赖于操作系统的实现细节。

这要求我有一个任意命名的类似静态构造函数的函数,我会在类声明之后立即调用它。(用这种方法困扰我的另一件事)。

当然,我可以$adaptee在每个方法调用上初始化一个局部变量,但这似乎不合适,我必须在需要适配器的每个其他静态函数中复制它。

现在......对于 PHP 的类实现细节:它们不是一流的对象,所以我不能只将类作为参数传递。在示例中,它要求我将 Adaptees 创建为非静态(这个术语是什么?)类,然后实例化它并最终将其分配给$adapteeAdapter 类的静态成员变量。

或许这只是我的这种奇怪的、完全主观的想法……但我真的觉得这样不合适。您对更好的实施有任何想法吗?

我的另一个想法是,改为存储适配者的类名,然后使用call_user_func,但我觉得使用这种方法不太舒服。

更新

我可能没有正确描述这一点,所以我将尝试在更新中解释这一点:

我不是在寻找如何获得底层操作系统,但我想有一个简洁的方法,让静态类根据操作系统是 Linux、Windows、FreeBSD 还是其他东西而采取不同的行动。

我想到了适配器模式,但是由于我没有静态构造函数,因此我无法真正初始化该类。一种方法是在每个公共静态方法调用开始时对其进行初始化(或仅检查它是否已初始化)。

另一种可能性是,创建一个类似于静态构造函数的方法,并在声明后立即调用它。这可能会奏效,但我只是想知道还有哪些其他可能更优雅的方法来实现这一目标。

至于我最初的例子:它应该是一个实用函数,它实际上不需要保存任何类型的状态,所以我不是在寻找任何类型的路径对象。我想要的是一个路径工厂函数,它返回一个字符串,而不必在每次调用时区分不同的操作系统。“库”——让我创建了一个静态类作为我的相关实用程序函数的伪命名空间,以及适配器模式需要支持的不同实现细节。现在我正在寻找一种优雅的方式,将两者结合起来。

4

2 回答 2

4

当你让它们静止时,你会在脚上开枪。您不能注入静态类,因此您将始终与全局范围耦合,并且因为您将在任何地方对静态调用进行硬编码,维护它们将成为一场噩梦。而且你也不能模拟它们(好吧,PHPUnit 可以,但它只允许测试否则无法测试的代码)。

只需创建一个实例并使用常规函数,就可以省去一些后顾之忧。使用静力学没有任何优势。性能影响完全可以忽略不计。

我可能会为适配者和要实现的适配器创建一个接口

interface IPathAdapter
{
    public function cleanPath($path);
    public function isAbsolutePath($part);
    // more …
}

然后可能会做类似的事情

class Path implements IPathAdapter
{
    protected $_adapter;

    public function __construct(IPathAdapter $adapter)
    {
        $this->_adapter = $adapter;
    }

    public function cleanPath($path)
    {
        $this->_adapter->cleanPath($part);
    }

    public function isAbsolutePath($part)
    {
        $this->_adapter->isAbsolutePath($part);
    }

    // more …

    public function join(){
        $parts = func_get_args();
        $joined = array($this->getScriptPath());
        foreach($parts as $part){
            $part = $this->cleanPath($path);
            if ($this->isAbsolutePath($part)){
                $joined = array($part);
            } else{
                $joined[] = $part;
            }
        }
        return implode($this->getPathSeparator(), $joined);
    }
}

所以当我想使用路径时,我必须这样做

$path = new Path(new PathAdapter_Windows);

如果您无法注入适配器,我可能会走您已经建议的路线并将适配器类名称作为参数传递,然后从 Path 中实例化它。或者我会将适当适配器的检测完全留给 Path 类,例如让它检测操作系统,然后实例化所需的内容。

如果您想自动检测,请查看PHP 是否具有检测其运行的操作系统的功能?. 我可能会编写一个单独的类来处理标识,然后使其成为 Path 类的依赖项,例如

public function __construct(IDetector $detector = NULL)
{
    if($detector === NULL){
        $detector = new OSDetector;
    }
    $this->_detector = $detector; 
}

我注入的原因是因为它允许我更改实现,例如在 UnitTests 中模拟检测器,但也可以忽略在运行时注入。然后它将使用默认的 OSDetector。使用检测器,检测操作系统并在路径或专用工厂中的某处创建适当的适配器。

于 2010-12-16T14:38:55.490 回答
0

我认为您可以这样做,您只需将命名空间路径放入全局变量中,例如在 composer autoload.php 中:

$GLOBALS['ADAPTED_CLASS_NAMESPACE'] = 'MyComponent\AdapterFoo\VendorBar';

我认为在不能使用依赖注入的情况下,即在实体中进行验证,这是一种很好的方法(我们记住,分离的验证类更好)。

<?php

namespace MyComponent;

use MyComponent\AdaptedInterface;
use ReflectionClass;

class Adapter
{
    /**
     * @var AdaptedInterface
     */
    protected $adaptedClass;

    public function __construct(AdaptedInterface $validator = null)
    {
        if (null == $validator && $this->validateClassPath($GLOBALS['ADAPTED_CLASS_NAMESPACE'])) {
            $this->adaptedClass = new $GLOBALS['ADAPTED_CLASS_NAMESPACE'];
        } else {
            $this->adaptedClass = $validator;
        }
    }

    protected function validateClassPath($classPath)
    {
        $reflection = new ReflectionClass($classPath);

        if (!$reflection->implementsInterface(
            'MyComponent\AdaptedInterface'
        )) {
            throw new \Exception('Your adapted class have not a valid class path :' . $classPath . 'given');
        }

        return true;
    }
}

所以在任何地方:

(new Adapter())->foo($bar);
于 2016-01-08T14:59:20.900 回答