具有静态类的适配器模式

发布于 2024-10-08 14:30:48 字数 1733 浏览 0 评论 0原文

我正在寻找一种在 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 的类实现细节:它们不是一流的对象,所以我不能只将类作为参数传递。在示例中,它要求我将适应者创建为非静态(这个术语是什么?)类,然后实例化它并最终将其分配给静态$adaptee Adapter类的成员变量。

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

我的另一个想法是,存储适配器的类名,并使用 call_user_func 来代替,但我觉得使用这种方法不太舒服。

更新

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

我不是在寻找如何获取底层操作系统,但我希望有一个简洁的方法,让静态类以不同的方式表现取决于操作系统是 Linux、Windows、FreeBSD 还是其他操作系统。

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

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

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

I am looking for a good way to implement the Adaptor pattern with static classes in PHP 5.x.

One of the examples where I would like to use this, is as a counterpart to Python's os.path.join().
I would have two adaptees, a Windows and a Linux adaptee class.

I think it is reasonable, to implement these classes as static classes, because they have no "context". They do not need to store any state and creating an instance everytime I need one seems superfluous - therefore I am looking for a clean way to implement this.

Let's consider the following bogus implementation:

    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);
    }

The first thing you will notice is, that it assumes an initialized static member called adaptee which would hold the necessary, OS-dependent implementation details.

This requires me to have an arbitrarily named static constructor-like function, that I would call immediately after the declaration of the class. (Another thing that bothers me with this approach).

Of course, I could initialize a local $adaptee variable on each method call, but that seems like inappropriate and I would have to replicate that in each other static function that needs the adaptee.

Now... for PHP's class implemention detail: They are not first-class objects, so I couldn't just pass the class as an argument. In the example, it requires me to create the Adaptees as non-static (what is the term for this?) classes, then instantiate it and eventually assign it to the static $adaptee member variable of the Adapter class.

Maybe this is just this weird and completely subjective thought that I have... but I really feel that it is not appropriate to do it like this. Do you have any ideas about a better implementation?

One other idea that I've had is, to store the adaptee's class name instead, and use call_user_func instead, but I don't feel too comfortable using this approach.

Update

I may not have described this properly, so I will try to explain this in an update:

I am not looking on how to get the underlying Operating System, but I would like to have a neat way, for a static class to act differently depending on whether the OS is Linux, Windows, FreeBSD or something else.

I thought of the adaptor pattern, but since I don't have a static constructor, I cannot really initialize the class. One way would be to initialize it at the beginning of every public static method call (or just check whether it is initialized).

The other possibility would be, to create a static constructor-like method and simply call it right after the declaration. That might do the trick, but I am just wondering what other, possibly more elgeant methods there are, to achieving this.

As for my initial example:
It is supposed to be a utility function, it does not need to preserve state in any kind really, so I am not looking for a Path-Object of any sorts. What I would like, is a Path factory function, that returns a string, without having to differentiate between the different OSes every time when called. The "library"-thing led me to create a static class as pseudo-namespace for my related utility functions, and the different implementation details that need to be supported to the adaptor pattern. Now I am looking for an elegant way, to combine the two.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

指尖上的星空 2024-10-15 14:30:48

当你让它们静止时,你会搬起石头砸自己的脚。您无法注入静态类,因此您将始终与全局范围耦合,并且因为您将在各处对静态调用进行硬编码,所以维护它们将成为一场噩梦。而且您也不能模拟它们(好吧,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 时,我必须这样做

$path = new Path(new PathAdapter_Windows);

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

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

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

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

You'll shoot yourself in the foot when you make them static. You cannot inject static classes so you will always have coupling to the global scope and because you will hardcode static calls everywhere, maintaining them will become a nightmare. And you cannot mock them either (ok, PHPUnit can, but it only does to enable testing of code that otherwise would be untestable).

Just create an instance and use regular functions and save yourself some worries. There is no advantage in using statics. And the performance impact is utterly and totally negligible.

I'd probably create an interface for the adaptee and the adapters to implement

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

and then do probably something like

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);
    }
}

So when I want to use Path, I'd have to do

$path = new Path(new PathAdapter_Windows);

If you cannot inject the adapters, I'd probably go the route you already suggested and pass the Adapter class name as an argument to instantiate it from within Path then. Or I'd leave the detection of the appropriate adapter completely to the Path class, e.g. have it detect the OS and then instantiate what is needed.

If you want to autodetect, have a look at Does PHP have a function to detect the OS it's running on?. I'd probably write a separate class to handle the identification and then make it a dependency to the Path class, e.g.

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

The reason I am injecting is because it will allow me to change the implementation, e.g. to mock the Detector in UnitTests but can also ignore to inject at runtime. It will use the default OSDetector then. With the detector, detect the OS and create an appropriate adapter somewhere in Path or in a dedicated Factory.

请别遗忘我 2024-10-15 14:30:48

我认为你可以做到这一点,你只需要将名称空间路径放入全局变量中,例如在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);

I think you can do this, you just have to put the namespace path into a global var, for example in composer autoload.php:

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

I think it's a good approach in a context where you can't use dependency injection i.e in a entity for validation (we keep in mind that separated Validation classes are better).

<?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;
    }
}

So anywhere:

(new Adapter())->foo($bar);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文