Created
July 3, 2020 19:23
-
-
Save Solaxun/6f6e56052ed473b15846c26b297d6110 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(comment | |
Multimethods are an open dispatch mechanism, but is closed in it's | |
dispatch function. If the set of dispatch types is open, so is | |
the multimethod. | |
Protocols are always open because you can always add a new type. | |
Multimethods require you to pick something to dispatch on that | |
remains open so any user can create a new one. | |
For dependency injection, protocols work great because you can | |
construct the thing implementing the protocol ahead of time with | |
whatever data you need, and calling code only needs to call the | |
protocol method. Different implementations might have different | |
data stored in the record implementing the protocol, but the | |
protocol method signature remains consistent. For example, a | |
ISave might save data to SQL, which needs several arguments | |
(conn string, db, etc.) or a CSV, which needs one argument (path). | |
Constructing the record ahead of time to house the appropriate info | |
and then providing a simple protcol method `save` means calling | |
code doesn't have to change or know about the different required | |
data. | |
The object corrollary to this is similar to protocols, construct an | |
object with the required data in advance, and then provide methods | |
on that object with consistent signatures across subclasses to do the | |
work needed. | |
For multimethods, you can call with diff args as below, but then | |
for dependeny injection the calling code has to know that the argument | |
structure could change, which breaks DI. The way to make it like | |
protocols is to make sure that you embed all information you need | |
inside a collection/map that the multimethod receives, so that the | |
function signatures are consistent, and you just construct whatever | |
map/collection you need for the respective multi-method to do it's | |
work ahead of time. This is basically the same as protcols, where the | |
collection/map for multimethods corresponds to a record or reification | |
etc. implementing a protocol. | |
Usually you only dispatch on type, so defmulti is probably best for | |
dispatching on more than one type.) | |
;; works but notice different function signatures across multimethods | |
;; means calling code has to know about this - bad for DI, defeats the | |
;; point of using multimethods. | |
(defmulti save | |
(fn [storage & args] storage)) | |
(defmethod save :sql | |
[_ conn-string table db] | |
{:db db :conn conn-string :table table}) | |
(defmethod save :csv | |
[_ fpath] | |
fpath) | |
(save :sql "blah@dc1:foo" "table1" "db1") | |
(save :csv "marksstuff/saved/here.csv") | |
;; protcol version - object implementing protocol has | |
;; all the data you need, that is constructed in advance | |
;; and passed into the calling code so the calling code | |
;; is unchanged as the method signatures are consistent. | |
;; in this case, the signature only takes the type, but | |
;; it could take more args so long as they are consistent | |
(defprotocol ISave | |
(save [this])) | |
(defn sql-data [conn-string db table] | |
(reify ISave | |
(save [_] | |
{:db db :conn conn-string :table table}))) | |
(defn csv-data [fpath] | |
(reify ISave | |
(save [_] | |
fpath))) | |
(save (csv-data "marksstuff/saved/here.csv")) | |
(save (sql-data "blah@dc1:foo" "table1" "db1")) | |
;; making the multimethod signature consistent basically | |
;; mirroring the protocol version above. Map constructed | |
;; in advance by whoever wants to extend the persistence | |
;; layer with a new type to save, code that calls save | |
;; multimethod is unchanged, all data each implmentation | |
;; needs is part of the map. | |
(defmulti save | |
(fn [storage] (:storage-type storage))) | |
(defmethod save :sql | |
[{:keys [conn-string db table] :as storage}] | |
storage) | |
(defmethod save :csv | |
[storage-type] | |
(:fpath storage-type)) | |
(save {:storage-type :sql :db "db1" :conn "blah@dc1:foo" :table "table1"}) | |
(save {:storage-type :csv :fpath "marksstuff/saved/here.csv"}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Another way to make multi-method signatures consistent is to have a rest args which is ignored. So in the above example: