(ns v2
(:require [clojure.spec.alpha :as s]
[clojure.string :as str]))
(s/def ::distance (s/and number? pos?))
(s/def ::package string?)
(s/def ::destination string?)
(s/def ::delivery-type #{"air" "land" "sea"})
(defprotocol DeliveryService
(calculate-cost [this distance] "Calculates the delivery cost based on the distance.")
(process-delivery [this package destination] "Executes the delivery of the package to the destination.")
(estimated-time [this distance] "Estimates delivery time in hours."))
(defrecord AirDelivery []
DeliveryService
(calculate-cost [_ distance]
(* 5 distance))
(process-delivery [_ package destination]
(str "Package '" package "' will be delivered via Air to " destination))
(estimated-time [_ distance]
(Math/ceil (/ distance 800))))
(defrecord LandDelivery []
DeliveryService
(calculate-cost [_ distance]
(* 2 distance))
(process-delivery [_ package destination]
(str "Package '" package "' will be delivered via Land to " destination))
(estimated-time [_ distance]
(Math/ceil (/ distance 60))))
(defrecord SeaDelivery []
DeliveryService
(calculate-cost [_ distance]
(* 1 distance))
(process-delivery [_ package destination]
(str "Package '" package "' will be delivered via Sea to " destination))
(estimated-time [_ distance]
(Math/ceil (/ distance 30))))
(defmulti create-delivery-service
"Factory multi-method for creating delivery services"
(fn [type & _] (str/lower-case type)))
(defmethod create-delivery-service "air" [_]
(->AirDelivery))
(defmethod create-delivery-service "land" [_]
(->LandDelivery))
(defmethod create-delivery-service "sea" [_]
(->SeaDelivery))
(defmethod create-delivery-service :default [type]
(throw (ex-info "Invalid delivery type"
{:type type
:available-types #{"air" "land" "sea"}})))
(defn calculate-and-deliver
"Service that uses the factory to calculate the cost and deliver a package.
Returns a map with :cost, :delivery, and :estimated-time keys."
[type package destination distance]
{:pre [(s/valid? ::delivery-type type)
(s/valid? ::package package)
(s/valid? ::destination destination)
(s/valid? ::distance distance)]}
(try
(let [service (create-delivery-service type)]
{:cost (calculate-cost service distance)
:delivery (process-delivery service package destination)
:estimated-time (estimated-time service distance)})
(catch Exception e
(throw (ex-info "Delivery calculation failed"
{:cause (.getMessage e)
:type type
:package package
:destination destination
:distance distance})))))
(defn find-cheapest-delivery
"Finds the cheapest delivery service for given parameters"
[package destination distance]
(->> ["air" "land" "sea"]
(map #(-> [(calculate-and-deliver % package destination distance) %]))
(sort-by (comp :cost first))
first))
(comment
(calculate-and-deliver "air" "Electronics" "São Paulo" 1000)
;; => {:cost 5000
;; :delivery "Package 'Electronics' will be delivered via Air to São Paulo"
;; :estimated-time 2}
(find-cheapest-delivery "Heavy Machinery" "Porto Alegre" 800)
;; => [{:cost 800
;; :delivery "Package 'Heavy Machinery' will be delivered via Sea to Porto Alegre"
;; :estimated-time 27}
;; "sea"]
;; Validation error example
(calculate-and-deliver "air" "Books" "Curitiba" -100)
;; => Assertion Error: Invalid input
)
![Cover image for Clojure Is Awesome!!! [PART 5]](https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8zob7rc418omdle4e2yo.jpg)
Speedy emails, satisfied customers
Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.
For further actions, you may consider blocking this person and/or reporting abuse
Top comments (0)