在  “云端分布式架构下的编程语言: ELIXIR; Map” 一文中, 我们在 function route 中, 加入了 “Bears, Lions, Tigers” 到 map conv 中的 resp_body。

def route(conv) do
    # TODO: Create a new map that also has the response body:
    %{ conv | resp_body: "Bears, Lions, Tigers"}
end

但是, function route 目前只是具备了转换信息的功能; 将 resp_body 的空字串, 转换为: “Bears, Lions, Tigers”。

我们在本文中, 将运用 function clauses , 让 function route 能根据不同的 path, 而能转换 resp_body 为不同的值。

我们新增了另一个 request; path 是: /bears。

所以, 我们现在有两个 requests; 一个 request 的 path 是: /wildthings, 另一个request 的 path 是: /bears。

request = """
GET /wildthings HTTP/1.1
Host: example.com
User-Agent: ExampleBrowser/1.0
Accept: */*

"""
request = """
GET /bears HTTP/1.1
Host: example.com
User-Agent: ExampleBrowser/1.0
Accept: */*

"""

按照传统的编程方式, 我们就在 function route 中, 加入 if else。

def route(conv) do
    # TODO: Create a new map that also has the response body:
    if conv.path == "/wildthings" do
      %{ conv | resp_body: "Bears, Lions, Tigers"}
    else
      %{ conv | resp_body: "Teddy, Smokey, Paddington"}
    end
end

执行的结果是符合预期的:

$ elixir lib/servy/handler.ex
%{method: "GET", path: "/wildthings", resp_body: ""}
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 20

Bears, Lions, Tigers

%{method: "GET", path: "/bears", resp_body: ""}
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 25

Teddy, Smokey, Paddington

我个人是很不喜欢 if then else 的。因为, if then else 很容易的就会形成所谓的 Deeply Nested Logic 的技术债务; 相关的研究报告指出, Deeply Nested Logic 的技术债务, 是形成软件产品约 20% 缺陷的主要根因。

elixir 的函数式编程的方式, 运用 pattern matching 与 function clauses , 使得代码能避免掉 Deeply Nested Logic 技术债务的发生。

function clauses 指的是: 名字相同, arity (参数的数目) 也相同的 function。

function clauses :

  • 名字: route
  • arity (参数的数目) : 3
    • map conv
    • http 方法: “GET”
    • path: “/wildthings”, “/bears”
  • function clauses 中的:
    • 一个 function 负责 path 是: /wildthings 的信息转换。
    • 另一个 function 负责 path 是: /bears 的信息转换。
def route(conv, "GET", "/wildthings") do
    %{ conv | resp_body: "Bears, Lions, Tigers"}
end

def route(conv, "GET", "/bears") do
    %{ conv | resp_body: "Teddy, Smokey, Paddington"}
end

我们将使用 pattern matching 的方法, 在 function clauses 中, 找到相匹配 (match) 的 function, 以进行相对应 path 的信息转换。

因为, 在 function clauses 中, function 的 arity 为 3, 所以, function route(conv); arity 为 1; 需藉由另一个 function route(conv, conv.method, conv.path); arity 为 3; 在 function clauses 中, 找到相匹配 (match) 的 function。

def route(conv) do
    route(conv, conv.method, conv.path)
end

样例代码如下:

defmodule Servy.Handler do
  def handle(request) do
    request
    |> parse
    |> log
    |> route
    |> format_response

  end

  def log(conv), do: IO.inspect conv

  def parse(request) do
    # TODO: Parse the request string into a map:
    [method, path, _] =
    request
    |> String.split("\n")
    |> List.first
    |> String.split(" ")

    %{ method: method, path: path, resp_body: ""}
  end

  def route(conv) do
    route(conv, conv.method, conv.path)
  end

  def route(conv, "GET", "/wildthings") do
    %{ conv | resp_body: "Bears, Lions, Tigers"}
  end

  def route(conv, "GET", "/bears") do
    %{ conv | resp_body: "Teddy, Smokey, Paddington"}
  end

  def format_response(conv) do
    # TODO: Use values in the map to create an HTTP response string:
    """
    HTTP/1.1 200 OK
    Content-Type: text/html
    Content-Length: #{String.length(conv.resp_body)}

    #{conv.resp_body}
    """
  end

end


request = """
GET /wildthings HTTP/1.1
Host: example.com
User-Agent: ExampleBrowser/1.0
Accept: */*

"""

response = Servy.Handler.handle(request)
IO.puts response

request = """
GET /bears HTTP/1.1
Host: example.com
User-Agent: ExampleBrowser/1.0
Accept: */*

"""

response = Servy.Handler.handle(request)
IO.puts response

执行结果如下:

$ elixir lib/servy/handler.ex
%{method: "GET", path: "/wildthings", resp_body: ""}
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 20

Bears, Lions, Tigers

%{method: "GET", path: "/bears", resp_body: ""}
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 25

Teddy, Smokey, Paddington

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据