简体   繁体   English

通过回调参数类型紧密耦合的依赖关系

[英]Tightly coupled dependencies through callback argument types

My example uses PHP, but the concept should apply to OOP in general.我的示例使用 PHP,但这个概念应该适用于一般的 OOP。

I'm using the Dependency Injection pattern to decouple my classes and allow easy mocking and testing.我正在使用依赖注入模式来解耦我的类并允许轻松模拟和测试。 For this concrete example, my example class ApiConsumer performs HTTP requests against an API using an HTTP client, which is injected in the constructor through an interface:对于这个具体示例,我的示例类ApiConsumer使用 HTTP 客户端对 API 执行 HTTP 请求,该客户端通过接口注入构造函数:

class ApiConsumer {
    private $client;

    public function __construct(HttpClientInterface $client) {
        $this->client = $client;
    }
}

interface HttpClientInterface {
    public function async(string $method, string $uri, array $options, callable $success, callable $failure): void;
}

Now my issue is with those callbacks and parameter type hinting (in other languages, this would be an issue of type declaration).现在我的问题是那些回调和参数类型提示(在其他语言中,这将是类型声明的问题)。

One possible implementation of the interface is my class Guzzle , which wraps the GuzzleHttp library, a popular HTTP client lib for PHP.该接口的一种可能实现是我的类Guzzle ,它封装了 GuzzleHttp 库,这是一个流行的 PHP 客户端库。 This class looks like this:这个类看起来像这样:

class Guzzle implements HttpClientInterface {
    private $client;
    private $promises;

    public function __construct() {
        $this->client = new GuzzleHttp\Client;
    }

    public function async(string $method, string $uri, array $options, callable $success, callable $failure): void {
        $this->promises[] = function () use ($method, $uri, $options, $success, $failure) {
            return $this->client->requestAsync($method, $uri, $options)->then($success, $failure);
        };
    }
}

So it essentially parks the request in the $promises array until at some point, another method is called that actually runs those requests, executing either the success or failure callback depending on the request's result.因此,它本质上将请求停放在$promises数组中,直到在某个时刻调用另一个实际运行这些请求的方法,根据请求的结果执行成功或失败回调。

This leads to a problem when I'm writing those callback functions.当我编写这些回调函数时,这会导致问题。 In order to properly type hint the things that will actually end up getting passed to those callbacks, I need to directly reference the types used by the GuzzleHttp library.为了正确键入提示实际上最终会传递给这些回调的内容,我需要直接引用 GuzzleHttp 库使用的类型。 For example, say I'm making an API request inside my ApiConsumer class like this:例如,假设我在ApiConsumer类中发出 API 请求,如下所示:

    public function consumeSomeEndpoint(): void {
         $this->client->async(
             'GET', 
             'https://some.api.com/endpoint/', 
             [],
             function (Psr\Http\Message\ResponseInterface $response) {
                 var_dump($response);
             },
             function (GuzzleHttp\Exception\BadResponseException $reason) {
                 echo $reason->getResponse()->getBody();
                 throw new RuntimeException($reason->getMessage());
             },
         );
    }

The ResponseInterface is fine, that's general enough that it can be used in many places (the entire Psr package is essentially just providing HTTP related interfaces). ResponseInterface很好,足够通用,可以在很多地方使用(整个Psr包本质上只是提供与 HTTP 相关的接口)。 The BadResponseException , however, is specific to GuzzleHttp.BadResponseException ,但是,具体到GuzzleHttp。 If I want to create an alternate implementation of an HttpClientInterface , I have to actually import the GuzzleHttp lib just so I can understand those exceptions.如果我想创建HttpClientInterface的替代实现,我必须实际导入 GuzzleHttp 库,这样我才能理解这些异常。 I can't introduce an interface here because obviously the lib's Exceptions don't implement it.我不能在这里介绍接口,因为显然 lib 的 Exceptions 没有实现它。 The interface implemented by BadResponseException , Psr\\Http\\Message\\RequestInterface\\RequestExceptionInterface , doesn't provide the getResponse() method used to access the response payload, which contains potential information from the API about the reason for the failure. BadResponseException实现的接口Psr\\Http\\Message\\RequestInterface\\RequestExceptionInterface不提供用于访问响应负载的getResponse()方法,该方法包含来自 API 的有关失败原因的潜在信息。

Is there any way around this?有没有办法解决?

Yes, the way around this is to depend in an exception that is not tightly bound to the library, and that other replacements can either throw or implement.是的,解决这个问题的方法是依赖一个与库没有紧密绑定的异常,并且其他替换可以抛出或实现。

Luckily, that's the case for Guzzle.幸运的是,Guzzle 就是这种情况。

GuzzleHttp\\Exception\\BadResponseException extends GuzzleHttp\\Exception\\RequestException , which in turns implements Psr\\Http\\Message\\RequestInterface\\RequestExceptionInterface ; GuzzleHttp\\Exception\\BadResponseException扩展了GuzzleHttp\\Exception\\RequestException ,它Psr\\Http\\Message\\RequestInterface\\RequestExceptionInterface实现了Psr\\Http\\Message\\RequestInterface\\RequestExceptionInterface

As seen, here and here :正如所见, 这里这里

/**
 * Exception when an HTTP error occurs (4xx or 5xx error)
 */
class BadResponseException extends RequestException
{ /** class omitted **}
/**
 * HTTP Request exception
 */
class RequestException extends TransferException implements RequestExceptionInterface
{ /** class omitted **}

To type-hint properly the ecallback, you probably have to specify Psr\\Http\\Client\\ClientExceptionInterface , since that's the most generic one in the PSR contract, and conforming implementations might throw any of the extended exceptions: NetworkExceptionInterface or RequestExceptionInterface , mentioned earlier.要正确地对 ecallback 进行类型提示,您可能必须指定Psr\\Http\\Client\\ClientExceptionInterface ,因为这是 PSR 合约中最通用的一个,并且符合要求的实现可能会抛出任何扩展异常: NetworkExceptionInterfaceRequestExceptionInterface ,如前所述。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM