living without objects (thinking in functions)
TRANSCRIPT
OOP HANGOVER
DESIGN
DESIGNBefore After
MONOLITHIC DESIGN
OO DESIGNNoun-orientedVerb-orientedDomain-driven?
NOUNSCustomerDAOCustomerServiceCustomerController
VERBSRegisterCustomerPromoteCustomerToVIPRenderCustomerProfilePage
OO DESIGN PRINCIPLESSingle ResponsibilityInterface SegregationDependency Inversion
SINGLE RESPONSIBILITYpublic class ZipDownloadService
public List<File> downloadAndExtract(String location)
SINGLE RESPONSIBILITYpublic class FileDownloader
public List<File> downloadFiles(String location) ...
public class ZipExtractor
public File extractZip(File archive) ...
OR ... JUST FUNCTIONS(defn downloadfiles [location] (...))
(defn extractzip [archive] (...))
CLOJUREin a nutshell
( some-function arg1 arg2 arg3 )
INTERFACE SEGREGATIONpublic class ProductCatalog public ProductId Save(Product product) ...
public Product FindById(ProductId id) ...
INTERFACE SEGREGATIONpublic class ProductSaver public ProductId Save(Product product) ...
public class ProductFinder public Product FindById(ProductId id) ...
Somethin' ain't right
INTERFACE SEGREGATIONpublic class ProductRepository public ProductId Save(Product product) ...
public class ProductQuery public Product FindById(ProductId id) ...
Feelin' good now
OR ... JUST FUNCTIONS(defn saveproduct [product] (...))
(defn findproductbyid [id] (...))
Applying OO design principleseventually leads to...
functional design
WHAT'S MISSINGCode organizationEncapsulationInheritance hierarchiesPolymorphism
NO CODE ORGANIZATION?
(ns my.product.repository)
(defn save [product] (...))
(defn findbyid [id] (...))
(require '[my.product.repository :as productrepo])
(productrepo/findbyid 42)
NO ENCAPSULATION?
Data is not an objectData is immutable
NO INHERITANCE HIERARCHIES?
Blimey! What a showstopper
NO POLYMORPHISM?
We'll get there
COMPOSITION
COMPOSITIONAvoiding hard-coded dependencies
OO COMPOSITIONpublic class ProfilePage
public String render(Repository repository, int customerId) return toHtml(repository.loadProfile(customerId));
Repository repository = new Repository();ProfilePage page = new ProfilePage();
String html = page.render(repository, customerId);
FP COMPOSITION(defn renderpage [repositoryfn customerid] (tohtml (repositoryfn customerid)))
(defn loadprofile [customerid] (...))
(renderpage loadprofile customerid)
OO "DEPENDENCY INJECTION"ProfilePage pageInjected = new ProfilePage(new Repository());
pageInjected.render(customerId);
FP "DEPENDENCY INJECTION"(def renderinjected (fn [customerid] (renderpage loadprofile customerid)))
(renderinjected customerid)
PARTIAL APPLICATION(def renderinjected (partial renderpage loadprofile))
(renderinjected customerid)
"ADAPTER" PATTERN(defn parseint [s] (Integer/parseInt s))
(renderpage (comp loadprofile parseint) customerid)
(defn toviewmodel [profile] (...))
(renderpage (comp toviewmodel loadprofile) customerid)
"DECORATOR" PATTERN(defn withlogging [f] (fn [& args] (log/debug "Called with params" args) (def [result (apply f args)] (log/debug "Returned" result) result)))
(renderpage (withlogging loadprofile) customerid)
POLYMORPHISM
POLYMORPHISMI don't know.
I don't want to know.
POLYMORPHISMSubtypeParametricAd-hoc
OO POLYMORPHISMInheritance hierarchyInterfacesDependency inversion
public interface JsonObj String toJson();
public class JsonString implements JsonObj private final String value;
public JsonString(String value) this.value = value;
public String toJson() return "\"" + value + "\"";
public class JsonList implements JsonObj private final List<? extends JsonObj> list;
public JsonString(List<? extends JsonObj> list) this.list = list;
public String toJson() return "[" + list.stream() .map(JsonObj::toJson) .collect(joining(",")) + "]";
JsonObj obj = new JsonList(asList( new JsonString("a"), new JsonList(asList( new JsonString("b"), new JsonString("c") )), new JsonString("d")));
System.out.println(obj.toJson());
// ["a",["b","c"],"d"]
LIMITATIONS
Need wrapper typesCannot extend existing types
Too constraining!
FP POLYMORPHISMFunction compositionDispatch on parameters
PROTOCOLSopen type system
(defprotocol Json (tojson [this]))
(extendtype String Json (tojson [this] (str "\"" this "\"")))
(extendtype List Json (tojson [this] (str "[" (>> this (map tojson) (string/join ",")) "]")))
(extendtype nil Json (tojson [this] "null"))
(tojson ["a" ["b" "c"] nil "d"])
;;=> ["a",["b","c"],null,"d"]
Why stop there?
MULTIMETHODS
(defmulti greet :country)
(defmethod greet "LT" [person] (println "Labas," (:name person) ". Kaip sekasi?"))
(defmethod greet "FR" [person] (println "Bonjour," (:name person) "!"))
(defmethod greet :default [person] (println "Hi," (:name person)))
(greet :name "Jacques" :country "FR")
;;=> Bonjour, Jacques !
(defmulti say (fn [text n] (even? n)))
(defmethod say true [text n] (println text n "is even"))
(defmethod say false [text n] (println text n "is odd"))
(say "Guess what?" 5)
;;=> Guess what? 5 is odd
CONCLUSIONDesignCompositionPolymorphism
I'm probably over time already
(questions? "Osvaldas Grigas" @ogrigas)