{"id":2800,"date":"2011-10-14T19:36:37","date_gmt":"2011-10-14T23:36:37","guid":{"rendered":"http:\/\/www.ericfeminella.com\/blog\/?p=2800"},"modified":"2020-03-12T23:00:42","modified_gmt":"2020-03-13T03:00:42","slug":"circumventing-conditional-comparisons","status":"publish","type":"post","link":"https:\/\/www.ericfeminella.com\/blog\/2011\/10\/14\/circumventing-conditional-comparisons\/","title":{"rendered":"Circumventing Conditional Comparisons"},"content":{"rendered":"<p>Often during the course of my day I come across code which evaluates the same conditional comparisons in multiple contexts. Understandably, this is rather typical of most software systems, and while it may only introduce a negligible amount of technical dept (in the form of redundancy) for smaller systems, that dept can grow considerably in more complex, large scale applications. From a design perspective, this issue is applicable to nearly every language.<\/p>\n<p>For example, consider a simple <code>Compass<\/code> class which defines just one public property, &#8220;direction&#8221; and, four constants representing each cardinal direction: North, East, South and West, respectively. In JavaScript, this could be defined simply as follows:<\/p>\n<pre lang=\"javascript\">\r\nvar Compass = function(direction){\r\n    this.direction = direction;\r\n}\r\nCompass.NORTH = \"North\";\r\nCompass.EAST  = \"East\";\r\nCompass.SOUTH = \"South\";\r\nCompass.WEST  = \"West\";\r\n<\/pre>\n<p>Technically, there is nothing problematic with the above class signature; the defined constants certainly provide a much better design than conditional comparisons against literal strings throughout implementation code. That being said, this design does lead to redundancy as every instance of <code>Compass<\/code> which needs to evaluate the state of <code>direction<\/code> requires conditional comparisons. <\/p>\n<p>For example, to test for <code>Compass.North<\/code>, typically, client code must be implemented as follows: <\/p>\n<pre lang=\"javascript\">\r\nif (compass.direction === Compass.NORTH) { \r\n    \/\/...\r\n}\r\n<\/pre>\n<p>Likewise, simular comparisons would need to be implemented for each cardinal direction. And, while this may seem trivial for a class as simple as the <code>Compass<\/code> example, it does become a maintenance issue for more complex implementations. <\/p>\n<p>With this in mind, we can simplify client code by defining each state as a specific method of <code>Compass<\/code>. In doing so, we afford our code the benefit of exercising (unit testing) <code>Compass<\/code> exclusively. This alone improves maintainability while also simplifying client code which depends on <code>Compass<\/code>. As such, <code>Compass<\/code> could be refactored to:<\/p>\n<pre lang=\"javascript\">\r\nvar Compass = function(direction){\r\n    this.direction = direction;\r\n}\r\nCompass.prototype = {\r\n    isNorth: function() {\r\n        return this.direction === Compass.NORTH;\r\n    },\r\n    isWest: function() {\r\n        return this.direction === Compass.WEST;\r\n    },\r\n    isSouth: function() {\r\n        return this.direction === Compass.SOUTH;\r\n    },\r\n    isEast: function() {\r\n        return this.direction === Compass.EAST;\r\n    }\r\n}\r\n<\/pre>\n<p>Based on the above implementation of <code>Compass<\/code>, the previous conditional comparison can be refactored as follows:<\/p>\n<pre lang=\"javascript\">\r\nif (compass.isNorth()) { \r\n    \/\/...\r\n}\r\n<\/pre>\n<h3>Comparator API<\/h3>\n<p>To simplify implementing conditional comparisons, I have provided a simple <code>Comparator<\/code> API that defines a single static method: <code>Comparator.each<\/code>, which allows for augmenting existing objects with comparison methods. <code>Comparator.each<\/code> can be invoked with three arguments as follows:<\/p>\n<table class=\"api-doc-table\" cellspacing=\"0\">\n<thead><\/thead>\n<tbody>\n<tr class=\"api-method\">\n<td>type<\/p>\n<td class=\"default\"><\/td>\n<\/tr>\n<tr>\n<td colspan=\"2\" class=\"desc\">The Class to which the comparison methods are to be added.<\/td>\n<\/tr>\n<tr class=\"api-method\">\n<td>property<\/td>\n<td class=\"default\"><\/td>\n<\/tr>\n<tr>\n<td colspan=\"2\" class=\"desc\">The property against which the comparisons are to be made. If the property has not been defined it, too, will be added.<\/td>\n<\/tr>\n<tr class=\"api-method\">\n<td>values<\/td>\n<td class=\"default\"><\/td>\n<\/tr>\n<tr>\n<td colspan=\"2\" class=\"desc\">An <code>Array<\/code> of constants where each value will be used to create a new comparison method (prefixed with &#8220;is&#8221;). If the constants specified are Strings, typically an <code>Array<\/code> containing each constant should suffice. For example, passing <code>[Foo.BAR]<\/code> where <code>BAR<\/code> equals &#8220;Bar&#8221; would result in an <code>isBar()<\/code> method being created. To specify custom comparison method names, an <code>Object<\/code> of name\/value pairs can be used where each name defines the name of the method added and the value is the constant evaluated by the method. This is useful for constants which are not strings. For example, <code>{isIOS421: DeviceVersion.IOS_4_2_1}<\/code> where <code>IOS_4_2_1<\/code> equals 4.2.1 would result in an <code>isIOS421()<\/code> method being created.<\/td>\n<\/tr>\n<\/table>\n<p>Taking the <code>Compass<\/code> example, the previous comparison methods could be augmented without the need to explicitly define them via <code>Comparator.each<\/code>:<\/p>\n<pre lang=\"javascript\">\r\nvar Compass = function(){\r\n    this.direction = direction;\r\n}\r\nCompass.NORTH = \"North\";\r\nCompass.EAST  = \"East\";\r\nCompass.SOUTH = \"South\";\r\nCompass.WEST  = \"West\";\r\nComparator.each( Compass, \"direction\", [Compass.NORTH, \r\n                                        Compass.WEST, \r\n                                        Compass.SOUTH, \r\n                                        Compass.EAST] );\r\n<\/pre>\n<p>The above results in the comparison methods <code>isNorth<\/code>, <code>isEast<\/code>, <code>isSouth<\/code> and <code>isWest<\/code> being added to the <code>Compass<\/code> type.<\/p>\n<p><strong>Comparator<\/strong>: <span class=\"figure\"><a href=\"https:\/\/gist.github.com\/2015278#file_comparator.js\" target=\"_blank\" rel=\"noopener noreferrer\">source<\/a> | <a href=\"https:\/\/gist.github.com\/2015278#file_comparator_min.js\" target=\"_blank\" rel=\"noopener noreferrer\">min<\/a> | <a href=\"https:\/\/gist.github.com\/2015278#file_comparator.test.js\" target=\"_blank\" rel=\"noopener noreferrer\">test<\/a> (<a href=\"http:\/\/code.ericfeminella.com\/apis\/javascript\/comparator\" target=\"_blank\" rel=\"noopener noreferrer\">run<\/a>)<\/span><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Often during the course of my day I come across code which evaluates the same conditional comparisons in multiple contexts. Understandably, this is rather typical of most software systems, and while it may only introduce a negligible amount of technical dept (in the form of redundancy) for smaller systems, that dept can grow considerably in more complex, large scale applications&#8230;. <a class=\"read-more\" href=\"https:\/\/www.ericfeminella.com\/blog\/2011\/10\/14\/circumventing-conditional-comparisons\/\">Continue Reading<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false}}},"categories":[42,44,23,78,31,45,35,40],"tags":[151,152,153,154,155],"class_list":["post-2800","post","type-post","status-publish","format-standard","hentry","category-apis","category-code-review","category-design-patterns","category-javascript-2","category-oop","category-refactoring","category-software-engineering","category-test-driven-development","tag-conditional-comparison","tag-equality-operator","tag-identity-operator","tag-javascript-equality-operator","tag-javascript-identity-operator"],"jetpack_publicize_connections":[],"aioseo_notices":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.ericfeminella.com\/blog\/wp-json\/wp\/v2\/posts\/2800","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.ericfeminella.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.ericfeminella.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.ericfeminella.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ericfeminella.com\/blog\/wp-json\/wp\/v2\/comments?post=2800"}],"version-history":[{"count":0,"href":"https:\/\/www.ericfeminella.com\/blog\/wp-json\/wp\/v2\/posts\/2800\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.ericfeminella.com\/blog\/wp-json\/wp\/v2\/media?parent=2800"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ericfeminella.com\/blog\/wp-json\/wp\/v2\/categories?post=2800"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ericfeminella.com\/blog\/wp-json\/wp\/v2\/tags?post=2800"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}