系列文章的目录:https://blog.youkuaiyun.com/hjkl950217/article/details/89490709
记住:设计模式重理解,轻照搬
背景
如果你进入微服务化,并且以http
的方式做远程调用的话,有没有觉得需要处理各种各样的http
需求?就我自己而言,我遇到下面的情况:
- 普通的调用
- 带Oauth认证头的调用,并且认证的token可能是从配置服务中获取的
- 返回的数据是文件流
- 记录请求和返回时的数据到日志中
- 支持自定义的格式,比如添加masagePack的支持
- 其它…
这些会经常用,但是又不想每次都自己手写。有没有办法可以方便的按需求配置http的调用呢? 答案是有的,这就是今天讲的用建造者模式构建httpClient
。
PS:示例中会用几个对象的模拟http调用中的对象,本文只是分享这种扩展的方式,而不是具体怎么编写httpClient.毕竟每个业务小组面对的http需求是不相同的。文章中只包括核心部分,具体细节可以下载附件中的代码1,很少。
什么是建造者模式?
官方的定义网上一查就有,这里我说说我的理解:用一系列的对象或委托,按需求快速
组装出我们使用的工具
.
有人用过万用螺丝刀么?就像下面这样:
这个工具有很多功能,而且使用时也能快速组装。但他的核心点其实只有一个,是他的通用螺口
只要工具头适配了这个螺口,就可以与握把组后成我们需要的工具。
而代码中的这种快速组合工具的方式,就是建造者模式。
分析问题
在http的调用中,我们的通用螺口
其实是header
,它的数据全是KV型的,标记了请求\响应行为中的具体参数细节,拿到了头,我们就知道怎么处理数据、做什么样的控制等等。那么我们只要把http中调用过程中,对请求与响应的过程开放
出来就好了
核心代码
来看看开放后的处理过程:
//只是模拟,并没有真实的http调用,所以这里的响应体是mock的
public Response SendHttp(Request request, Response mockResponse = null)
{
this.RequestActions.ForEach(t => t.Invoke(request));//调用外部注入的方法处理请求体
Console.WriteLine($"基础Http接口被调用!");
Console.WriteLine($"url:{request.Url}");
Console.WriteLine($"Http动作方法:{request.Method}");
Console.WriteLine("Header:");
foreach (var item in request.Headers)
{
Console.WriteLine($"headerName:{item.Key} \t value:{item.Value}");
}
Console.WriteLine($"body:\t\n{request.Body}");
Console.WriteLine();
mockResponse = mockResponse ?? new Response();
this.ResponseActions.ForEach(t => t.Invoke(mockResponse));//同上
return mockResponse;
}
这里的RequestActions
和ResponseActions
都是属性,由外部添加处理方法进来。然后在代码执行时对请求或响应进行处理。
然后再在建造者类中,进一步简化处理过程:
public static HttpClientBuilder UseOAuth(this HttpClientBuilder httpClientBuilder, string toekn)
{
Action<WebClient> useOauth = webClient =>
{
//这里还少了一个代码,思考下少了什么
webClient.RequestActions
.Add(t => t.Headers.Add(new KeyValuePair<string, string>("OAuth", toekn)));
};
httpClientBuilder.Actions.Add(useOauth);
return httpClientBuilder;
}
这里其实是对OAuth
头的进一步封装,让我们的工具头
可以快速适配!
学会这一个点,是不是可以自己再扩充其它的功能呢?比如上面提到的MasagePack
协议,只需要添加一套代码:判断到Content-Type
为msgPack
,则使用MasagePack的序列化器解析数据。
建造者的意义
其实你看上面的代码会发现UseOAuth
方法似乎只是一个封装而已,没什么用。其实不然。。
我对建造者的理解,其实是把他当成一个桥梁,将底层逻辑
与开发者
之间快速适配的桥梁
。 示例代码中的HttpClientBuilder
只是给扩展方法使用的,本身没什么逻辑。扩展方法中具体定义了如何处理请求或响应,然后由调用者非常方便的组合他们需要的工具。
毕竟,框架需要提供简单易用
的方法给别人呀。
使用
使用的地方其实很简单了:
private static void Main(string[] args)
{
Request request = new Request()
{
Url = "http://test.com",
Method = "POST",
Body = "{\"demo\":\"demoString\"}"
};
WebClient client = HttpClientBuilder.CreateDefaultHttpClientBuilder()
.BuildWebClient();
//此时的client只是最基础的client
client.SendHttp(request);
client = HttpClientBuilder.CreateDefaultHttpClientBuilder()
.UseOAuth("testToken")
.UseOAuth("testToken2")
.BuildWebClient();
//此时的client就拥有了处理OAuth头的能力
client.SendHttp(request);
Console.WriteLine("End");
Console.ReadLine();
}
输出:
有没有发现OAuth的头有2个? 现在知道少了什么代码不?这里引出一个潜规则出来。。
建造者模式的潜规则
如果是相同的方法被调用多次,只有最后一个方法生效才对!
上面缺失的代码中,其实是少了对Oauth头的判断:
- 如果已经存在 则更新里面的值
- 如果不存在,则新加
与工厂模式的区别
先说下建造者模式的缺点:强调方法被调用的顺序,需要使用者对建造过程有一定的熟悉。
知道缺点了,再来看与工厂模式的区别就很清楚了~
工厂模式更适合“品种少,变化不多,但构建过程复杂”
的场景,而建造者模式更适合"品种多样,变化很多,单个品种构建过程一般复杂"
的场景。 有的时候还可以联合使用!! 工厂模式在构建产品的具体代码时,使用建造者模式来构建。