面向对象设计与分析40讲(24)上下文context在软件设计中的应用--依赖注入的典型应用

2023-12-16 05:06:50

上下文

首先,我们应该谈谈什么是上下文。

上下文(Context)是指某个事件、任务或问题所处的特定环境或情境。它包含了相关的信息、条件、状态和对象,用于帮助理解、解释和处理当前的情况。

在计算机科学中,上下文可以指代以下几个方面:

  1. 程序执行上下文:指程序在执行过程中的当前状态。它包括程序计数器、堆栈、寄存器等信息,用于控制和管理程序的执行流程。
  2. 编程语言中的上下文:指编程语言中代码的执行环境。例如,在 JavaScript 中,函数的执行上下文由函数的作用域、变量、this 指向等组成。
  3. 通信协议中的上下文:指在通信过程中相关的信息和数据。例如,在网络通信中,每个数据包都可能包含一些额外的上下文信息,用于标识发送者、接收者、消息类型等。
  4. 自然语言处理中的上下文:指在理解自然语言文本时,需要考虑文本中前后文的信息,以获得更准确的语义理解。例如,在对话系统中,上下文可以是之前的对话历史或上下文回复。
  5. 用户界面中的上下文:指用户界面中当前的操作环境和状态。例如,在一个图形化界面中,上下文可以包括当前打开的窗口、选中的对象等。

总的来说,上下文提供了周围环境和相关信息来帮助理解和处理当前的问题或任务。它在不同的领域中具有不同的含义和用法,但都起到了关键的作用,提供了必要的背景和条件,以便进行适当的决策和操作。

当谈到依赖注入(Dependency Injection, DI)时,上下文(Context)可以用来传递依赖对象或依赖解析器,以满足组件所需的依赖关系。

比如图形学领域,contex相当于 this 或者 self或者是句柄,用来指向当前绘图环境用到的实例,不同的context还会形成堆栈关系。

对于多线程,协程之类的,需要在切换时保存现场以及相关的参数,相关参数就是context。

在异步回调应用中,上下文(Context)通常用于传递异步任务的状态和结果信息。当一个异步任务被触发后,它会在后台运行,并在完成时通知应用程序。此时,应用程序需要处理这个任务的返回结果,可能需要进行一些后续操作或者将结果显示给用户。

为了方便处理异步任务的结果,通常会使用上下文对象来存储相关信息。当异步任务完成后,它会将结果存储到上下文对象中,并触发一个回调函数来通知应用程序。应用程序可以通过回调函数获取上下文对象中的结果,并进行相应的处理。

对于面向对象的应用程序,尽管application实例只有一个,但是在实际编码中,如果不去定义context,就很容易发现一些工具类或者服务管理器为了写代码方便会被错误实现为单例模式。而实际上,考虑到代码可测性,一个重要的概念就是,依赖是可以被替换的,而单例模式就是难以替换,因此将依赖封装到context里去,就是一种实现技巧。

典型应用

下面我们列举一些典型的应用:

  1. 信号/异常捕获里的上下文信息
#include <signal.h>
#include <ucontext.h>

static void sig_handler(int signo, siginfo_t *siginfo, void *context) {
    // 获取当前线程的 ucontext_t 结构体
    ucontext_t *ucontext = (ucontext_t *) context;

    // 访问 ucontext_t 结构体中的相关字段,如 RIP 寄存器
    printf("Program counter: 0x%llx\n", ucontext->uc_mcontext.gregs[REG_RIP]);
    // ...
}

int main() {
    struct sigaction act;
    act.sa_flags = SA_SIGINFO;  // 为了在信号处理函数中获取额外信息,需要设置 SA_SIGINFO 标志位
    act.sa_sigaction = sig_handler;
    if (sigaction(SIGSEGV, &act, NULL) < 0) {
        perror("sigaction");
        return -1;
    }

    // ...
}

在上述示例中,sigaction 函数用于注册一个信号处理函数,当程序接收到 SIGSEGV 信号时,会自动调用 sig_handler 函数。该函数的第三个参数 context 就是 ucontext_t 类型的指针,用于保存程序执行状态的信息。在示例中,我们通过访问 ucontext_t 结构体中的相关字段,比如 RIP 寄存器,来获取程序执行状态的信息。

  1. 服务器的响应
#include <iostream>
#include <memory>
#include <string>

#include <grpcpp/grpcpp.h>

#include "helloworld.grpc.pb.h"

using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
using helloworld::HelloRequest;
using helloworld::HelloResponse;
using helloworld::Greeter;

class GreeterServiceImpl final : public Greeter::Service {
    Status SayHello(ServerContext* context, const HelloRequest* request, HelloResponse* response) override {
        std::string prefix("Hello, ");
        response->set_message(prefix + request->name());
        return Status::OK;
    }
};

void RunServer() {
    std::string server_address("0.0.0.0:50051");
    GreeterServiceImpl service;

    ServerBuilder builder;
    builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
    builder.RegisterService(&service);

    std::unique_ptr<Server> server(builder.BuildAndStart());
    std::cout << "Server listening on " << server_address << std::endl;
    server->Wait();
}

int main(int argc, char** argv) {
    RunServer();
    return 0;
}

在 gRPC 的 C++ 中,ServerContext 类提供了许多有用的信息和功能,可以在服务器端访问和控制 RPC 请求。以下是一些 ServerContext 提供的主要功能:

  1. 元数据(Metadata):通过 ServerContext 可以获取到请求中的元数据。元数据是以键值对形式存储的请求附加信息,例如身份验证凭证、客户端信息等。
cppCopy Codeconst grpc::string& GetMetadata(const grpc::string& key)

通过调用 GetMetadata 函数,可以获取特定键的元数据值。例如,可以使用 “authorization” 键来获取身份验证凭证。

  1. 客户端地址(Client Address):ServerContext 还提供了获取客户端 IP 地址和端口号的函数。
cppCopy Codeconst grpc::string& peer() const

通过调用 peer 函数,可以获取客户端的 IP 地址和端口号。

  1. 截止时间(Deadline):ServerContext 还提供了用于获取 RPC 请求截止时间的函数。
cppCopy Codestd::chrono::system_clock::time_point deadline() const

通过调用 deadline 函数,可以获取请求的截止时间。这对于实现超时控制和处理长时间运行的 RPC 请求非常有用。

  1. 调用取消(Cancellation):ServerContext 还提供了用于检查和响应 RPC 请求是否被取消的功能。
cppCopy Codebool IsCancelled() const

通过调用 IsCancelled 函数,可以检查 RPC 请求是否被取消。如果返回 true,则表示请求已被取消。

除了上述功能之外,ServerContext 还提供了其他一些有用的函数和方法,用于控制和访问 RPC 请求的各个方面。可以参考 gRPC 的 C++ API 文档以获取更详细的信息。

context的标准范例

#include <iostream>
#include <string>

// 定义上下文类
class Context {
public:
    Context(const std::string& name) : m_name(name) {}
    const std::string& getName() const {
        return m_name;
    }

private:
    std::string m_name;
};

// 定义服务类
class Service {
public:
    virtual void doSomething(const Context& context) = 0;
};

// 实现服务类
class HelloService : public Service {
public:
    virtual void doSomething(const Context& context) override {
        std::cout << "Hello, " << context.getName() << "!" << std::endl;
    }
};

// 主函数
int main() {
    // 创建上下文对象
    Context context("World");
    // 使用服务
    HelloService helloService;
    helloService.doSomething(context);
    return 0;
}

在上述示例中,我们定义了一个 Context 类来表示上下文,它包含了一个 name 属性。然后,我们定义了一个 Service 类作为服务接口,其中包含了 doSomething() 方法,它接受一个 Context 对象作为参数。

接着,我们实现了 HelloService 类,它继承自 Service 类,并实现了 doSomething() 方法,在方法中打印出问候语和上下文对象中的名称。

最后,在 main() 函数中,我们创建了一个名为 contextContext 对象,并将其传递给 HelloService 的实例的 doSomething() 方法。这样,就可以在控制台输出 “Hello, World!” 的问候语了。

这是一个非常简单的示例,实际上,在实际开发中,上下文(Context)可能包含更多的属性和方法,以支持特定的场景和需求。

文章来源:https://blog.csdn.net/HandsomeHong/article/details/135027503
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。