Шаблон адаптера со статическими классами - PullRequest
4 голосов
/ 16 декабря 2010

Я ищу хороший способ реализации шаблона Adapter со статическими классами в 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 как нестатические (как это называется?) Классы, затем создал его экземпляр и в конечном итоге назначил его статической переменной-члену $adaptee адаптера класс.

Может быть, это просто странная и совершенно субъективная мысль, которая у меня есть ... но я действительно чувствую, что неуместно делать это так. У вас есть идеи по поводу лучшей реализации?

Еще одна идея, которая у меня возникла, заключается в том, чтобы вместо этого сохранить имя класса адаптера и использовать call_user_func вместо этого, но я не чувствую себя слишком комфортно, используя этот подход.

Обновление

Возможно, я не описал это должным образом, поэтому я попытаюсь объяснить это в обновлении:

Я не смотрю на то, как получить базовую операционную систему, но мне бы хотелось, чтобы статический класс действовал по-разному в зависимости от того, является ли ОС Linux, Windows, FreeBSD или чем-то еще.

Я думал о шаблоне адаптера, но поскольку у меня нет статического конструктора, я не могу действительно инициализировать класс. Одним из способов было бы инициализировать его в начале каждого открытого статического вызова метода (или просто проверить, инициализирован ли он).

Другой возможностью было бы создать статический конструктор-подобный метод и просто вызвать его сразу после объявления. Это могло бы сработать, но мне просто интересно, какие еще, возможно, более изящные методы существуют для достижения этой цели.

Что касается моего начального примера: Предполагается, что это служебная функция, ей действительно не нужно сохранять состояние в каком-либо виде, поэтому я не ищу объект Path любого типа. То, что я хотел бы, это функция фабрики Path, которая возвращает строку без необходимости различать различные ОС каждый раз при вызове. «Библиотека» привела меня к созданию статического класса в качестве псевдо-пространства имен для моих связанных служебных функций и различных деталей реализации, которые необходимо поддерживать в шаблоне адаптера. Сейчас я ищу элегантный способ, чтобы объединить их.

Ответы [ 2 ]

3 голосов
/ 16 декабря 2010

Вы будете стрелять себе в ногу, когда сделаете их статичными. Вы не можете внедрять статические классы, поэтому у вас всегда будет связь с глобальной областью действия, и поскольку вы будете жестко кодировать статические вызовы повсюду, их обслуживание станет кошмаром. И вы также не можете имитировать их (хорошо, 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 или на выделенной фабрике.

0 голосов
/ 08 января 2016

Я думаю, что вы можете сделать это, вам просто нужно поместить путь к пространству имен в глобальную переменную, например в compoloser 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);
...