您如何使用(父)class 作为其他(子)class 的网关/接口?

How do you use a (parent) class as a gateway / interface to other (child) classes?

假设您有一个名为 'Parser' 的 class,它充当其他 class 的管理器,每个都与不同类型的解析器相关。我希望能够调用主解析器 class 并让它与特定的解析器提供程序交互。

例如:

$response = $this->Parser->setProvider('ParserA')->setTemplate($template)->parse($data);

这就是我目前对主要解析器 class 的看法,但我觉得有更好的方法,我也 运行 遇到了限制:

class Parser
{
private $provider;
public $template;

private function callMethod($provider, $method, $data)
{
    $response = false;

    $class = 'ServiceProviders\Parser\'.ucfirst($provider);

    if(method_exists($class, $method)) {

        $classInstance = new $class;

        $response = $classInstance->$method($data);

    } else {

        throw new \Exception('INVALID METHOD: '.$class.'->'.$method);
    }

    return $response;
}

public function setProvider($provider)
{
    $this->provider = $provider;

    return $this;
}

public function setTemplate($template)
{
    $this->template = $template;

    return $this;
}

public function parse($data)
{
    $result = null;

    try {

        $result = $this->callMethod($this->provider, __FUNCTION__, $data);

    } catch(\Exception $e) {

        throw new \Exception('There was a problem calling the parser: '.$e->getMessage());
    }

    return $result;
}
}

每个解析器提供程序、ParserA、ParserB 都有解析方法。我在使用这种方法时遇到的问题是 ParserA 需要模板而 ParserB 不需要。

我是强迫症,不想把传递给parse函数的参数变成数组。尽管简单的解决方案是这样做:

$parameters = array('data' => $data, 'template' = null);

 callMethod($provider, $method, $parameters);

我想要做的是使用 setTemplate() 方法在主解析器 class 中设置 ParserA 的 $template 值。

我已经尝试了一些方法来做到这一点。我尝试将 Parser class 变成一个接口,但这并没有解决任何问题(或者我不明白)。我尝试让 ParserA 扩展 Parser,但后来我无法调用 ParserA 的特定 parse() 方法,因为它随后成为父类的方法。

有没有办法实现我忽略的这个?

我认为您的方法在这里略有偏差:Parser 是 class 与 ParsingHandler 不同的一种,将两者混为一谈可能无济于事。你的要求是 classic Strategy Pattern 东西。

您的解析器应该提供解析的能力,但它不应该知道进行解析实际需要什么。将其交给 ParsingHandler(在我的示例中,如下所示)。 ParsingHandler 可以做任何它想做的事情,只要它提供了一个 Parser 将调用的方法来进行实际的解析:在我的例子中 parse().

这是一个骨架:

class Parser {

    private $parsingHandler;

    function __construct($parsingHandler){
        $this->parsingHandler = $parsingHandler;
    }

    function parse(){
        return $this->parsingHandler->parse();
    }

}

class TemplatedParsingHandler {

    private $template;

    function __construct($template){
        $this->template = $template;
    }

    function parse(){
        // use the template when parsing
    }
}

class DefaultParsingHandler {

    function parse(){
        // just parses stuff
    }
}

$myTemplatedParsingHandler = new TemplatedParsingHandler($myTemplate);
$myDefaultParsingHandler = new DefaultParsingHandler();

$myTemplatedParser = new Parser($myTemplatedParsingHandler);
$myDefaultParser = new Parser($myDefaultParsingHandler);

$myTemplatdResult = $myTemplatedParser->parse();
$myUntemplatedResult = $myDefaultParser->parse();

所以 TemplatedParsingHandler 知道它需要一个模板,但是 Parser 不需要 知道。这是它的 none 业务:它不取决于 TemplatedParsingHandler 如何进行解析,它只需要知道它的 ParsingHandler 有一个 parse() 方法。

可以使用接口对 ParseHandlers 强制执行最低 parse() 要求,但 PHP 是一种动态语言,因此这有点违背其预期的编码方法。鸭子绑在这里很好。如果你初始化解析器的对象提供了一个 parse() 方法,你不需要比它更明确。

根据此处的评论和对工厂模式的其他研究的见解,我找到了解决我的问题的解决方案。它还提供了一个不使用静态调用的工厂方法。

class Parser
{
private $ParserProvider;

public function __construct( string $provider )
{

    $this->ParserProvider = new \ReflectionClass($provider);

}

public function __invoke($data, $template = null)
{

    $Parser = $this->ParserProvider->newInstance();

    if(isset($template)) {

        return $Parser->setTemplate($template)->parse($data);

    } else {

        return $Parser->parse($data);
    }
}

}

并创建一个没有模板的新解析器:

$ParserA = new Parser('ParserA');
$result = $ParserA($contents);

并使用模板创建新的解析器:

$ParserB = new Parser('ParserB');
$result = $ParserB($contents, $template);