12

Although the below example seems a bit strange, it's because I'm trying to reduce a fairly large problem I've got at present to a minimal example. I'm struggling to work out how to call into multimethods when they're sitting behind a couple of abstraction layers and the defmulti and corresponding defmethods are defined in multiple namespaces. I really feel like I'm missing something obvious here...

Suppose I've got the following scenario:

  • I purchase stuff from a variety of suppliers, via their own proprietary interfaces
  • I want to implement a common interface to talk to each of those suppliers
  • I want to be able to purchase different items from different suppliers

Using Clojure, the recommended ways of implementing a common interface would be via protocols or multimethods. In this case, as I'm switching based on the value of the supplier, I think the best way to handle the situation I'm describing below is via multimethods (but I could be wrong).

My multimethod definitions would look something like this, which defines a common interface I want to use to talk to every supplier's APIs:

(ns myapp.suppliers.interface)
(defmulti purchase-item :supplier)
(defmulti get-item-price :supplier)

For each supplier, I probably want something like:

(ns myapp.suppliers.supplier1
  (:require [myapp.suppliers.interface :as supplier-api]))
(defmethod purchase-item :supplier1 [item quantity] ...)
(defmethod get-item-price :supplier1 [item] ...)

and

(ns myapp.suppliers.supplier2
  (:require [myapp.suppliers.interface :as supplier-api]))
(defmethod purchase-item :supplier2 [item quantity] ...)
(defmethod get-item-price :supplier2 [item] ...)

So far, no problem

Now to my code which calls these abstracted methods, which I assume looks something like:

(ns myapp.suppliers.api
  (:require [myapp.suppliers.supplier1 :as supplier1]
            [myapp.suppliers.supplier2 :as supplier2])
(defn buy-something
  [supplier item quantity]
  (purchase-item [supplier item quantity])
(defn price-something
  [supplier item]
  (get-item-price [supplier item])

This is starting to look a bit ... ugly. Every time I implement a new supplier's API, I'll need to change myapp.suppliers.api to :require that new supplier's methods and recompile.

Now I'm working at the next level up, and I want to buy a widget from supplier2.

(ns myapp.core
  (:require [myapp.suppliers.api :as supplier])
(def buy-widget-from-supplier2    
  (buy-something :supplier2 widget 1)

This can't work, because :supplier2 hasn't been defined anywhere in this namespace.

Is there a more elegant way to write this code? In particular, in myapp.core, how can I buy-something from :supplier2?

4

1 に答える 1

12

最初のメモ

例を単純化する過程でいくつかのことを混同したのか、それとも最初から正しくなかったかを判断するのは困難です。私が言及していることの例として、 を考えてみましょpurchase-itemget-item-price

  • 呼び出しは引数が 1 つのdefmulti関数です
  • defmethod呼び出しはそれぞれ 2 つの引数を取ります
  • の呼び出しはbuy-somethingベクトルを に渡しますが、ベクトルpurchase-item:supplierキーワードを検索すると常に返されますnil

あなたの懸念

  • 新しいサプライヤーの API を実装するたびに、myapp.suppliers.api を :require その新しいサプライヤーのメソッドに変更して再コンパイルする必要があります。

    • myapp.suppliers.interface名前空間が必要な場合はmyapp.suppliers.api、問題を回避できます
  • :supplier2 がこの名前空間のどこにも定義されていないため、これは機能しません。

    • 簡単に言えば、これは機能します。:)
  • このコードを書くためのよりエレガントな方法はありますか? 特に、myapp.core で、:supplier2 から何かを購入するにはどうすればよいですか?

    • 確かに、しかしこの解決策は、最初のメモのあいまいさに基づいて、いくつかの仮定を行います。

元の設計から大きく逸脱することなく、達成しようとしていたことを私がどのように解釈するかの完全に機能する例を次に示します。

  • myapp.suppliers.interface

    (ns myapp.suppliers.interface)
    
    (defmulti purchase-item (fn [supplier item quantity] supplier))
    
  • myapp.suppliers.supplier1

    (ns myapp.suppliers.supplier1
      (:require [myapp.suppliers.interface :as supplier-api]))
    
    (defmethod supplier-api/purchase-item :supplier1 [supplier item quantity]
      (format "Purchasing %dx %s from %s" quantity (str item) (str supplier)))
    
  • myapp.suppliers.supplier2

    (ns myapp.suppliers.supplier2
      (:require [myapp.suppliers.interface :as supplier-api]))
    
    (defmethod supplier-api/purchase-item :supplier2 [supplier item quantity]
      (format "Purchasing %dx %s from %s" quantity (str item) (str supplier)))
    
  • myapp.suppliers.api

    (ns myapp.suppliers.api
      (:require [myapp.suppliers.interface :as interface]))
    
    (defn buy-something [supplier item quantity]
      (interface/purchase-item supplier item quantity))
    
  • myapp.core

    (ns myapp.core
      (:require [myapp.suppliers.api :as supplier]))
    
    (def widget {:id 1234 :name "Monchkin"})
    
    (supplier/buy-something :supplier1 widget 15)
    ;;=> "Purchasing 15x {:id 1234, :name \"Monchkin\"} from :supplier1"
    
    (supplier/buy-something :supplier2 widget 3)
    ;;=> "Purchasing 3x {:id 1234, :name \"Monchkin\"} from :supplier2"
    

ご覧のとおり、supplier/buy-something呼び出しは適切なマルチメソッドの実装に伝達されます。うまくいけば、これはあなたが行こうとしていた場所にたどり着くのに役立ちます.

于 2016-09-20T11:03:42.767 に答える