Last active
January 8, 2019 10:33
-
-
Save patrickkunka/2019d6fe45abf9d5c5f8e764d05539d3 to your computer and use it in GitHub Desktop.
Facade Patterns
This file contains hidden or 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
/** | |
* Facade #1: Public/Private Members | |
* | |
* In this example, a "Facade" is placed between the consumer | |
* and the implementation in order to cherry-pick which public | |
* members we wish to expose to the API. | |
*/ | |
class Implementation { | |
constructor() { | |
/** | |
* @private | |
*/ | |
this.myPrivateProp1 = false; | |
/** | |
* @private | |
*/ | |
this.myPrivateProp2 = -1; | |
/** | |
* @public | |
*/ | |
this.myPublicProp1 = ''; | |
/** | |
* @public | |
*/ | |
this.myPublicProp2 = null; | |
Object.seal(this); | |
} | |
/** | |
* @private | |
*/ | |
myPrivateMethod1() { | |
// Do something private | |
} | |
/** | |
* @private | |
*/ | |
myPrivateMethod2() { | |
// Do something private | |
} | |
/** | |
* @public | |
*/ | |
myPublicMethod1() { | |
// Do something public | |
} | |
/** | |
* @public | |
*/ | |
myPublicMethod2() { | |
// Do something public | |
} | |
} | |
class Facade { | |
constructor() { | |
// A private instance of the implemetation is created | |
// within the facade's closure | |
const _ = new Implementation(); | |
// Members are then exposed one by one: | |
// Methods are lexically bound to the implementation instance | |
this.myPublicMethod1 = _.myPublicMethod1.bind(_); | |
this.myPublicMethod2 = _.myPublicMethod2.bind(_); | |
// Properties can be made read-only by only exposing | |
// "getter" functions | |
Object.defineProperties(this, { | |
myPublicProp1: { | |
get: () => _.myPublicProp1 | |
}, | |
myPublicProp2: { | |
get: () => _.myPublicProp2 | |
} | |
}); | |
// NB: Rather than using the ES6 `get()/set()` syntax on the | |
// prototype, we must define getters within the constructor closure | |
// using `Object.defineProperties()` to provide access to the private | |
// implentation instance variable (`_`) | |
// The facade is always stateless and can be frozen for an extra | |
// layer of robustness. | |
Object.freeze(this); | |
// If monkey patching or spies are expected, Object.seal(this) | |
// will suffice | |
} | |
} | |
// Only the facade is exported | |
export default Facade; |
This file contains hidden or 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
/** | |
* Facade #2: Factories and Facades | |
* | |
* Hiding the implementation can hinder development and | |
* debugging when we actually need to interrogate the state | |
* of something private. | |
* | |
* By passing a `debug` option, we can have our factory | |
* return the full implementation when needed. | |
*/ | |
import Facade from './Facade'; | |
import Implementation from './Implementation'; | |
export default function factory(config={}) { | |
if (config.debug === true) { | |
return new Implementation(config); | |
} | |
return new Facade(config); | |
} |
This file contains hidden or 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
/** | |
* Facade #3: Interfaces | |
* | |
* Facades can be used like "interfaces" in other languages, to | |
* establish an API contract that consumer code must implement. This | |
* is particularly useful when we open up our software to integration | |
* with consumer-provided "plugins". | |
* | |
* In this example, all consumer provided plugins must implement the | |
* `IPlugin` interface. | |
*/ | |
/** --------- Consumer Code ---------- */ | |
class PluginImplementation { | |
// An arbitrary implementation of a "plugin" for some | |
// vendor provided software | |
} | |
// As the public API is defined by `IPlugin` (see below), we simply need to | |
// extend it to create a facade for the plugin. We could also expose additional | |
// public members at this point. | |
class PluginFacade extends IPlugin {} | |
// Access to the implementation class is provided via a static | |
// `Implementation` property on the Facade | |
PluginFacade.Implementation = PluginImplementation; | |
export default PluginFacade; | |
/** --------- Vendor Code ---------- */ | |
class IPlugin { | |
constructor() { | |
// The implemenation class is accessed via a static property | |
// of the constructor (the plugin facade). | |
const plugin = new this.constructor.Implementation(); | |
this.myPublicMethod1 = plugin.myPublicMethod1.bind(plugin); | |
this.myPublicMethod2 = plugin.myPublicMethod2.bind(plugin); | |
Object.defineProperties(this, { | |
myPublicProp1: { | |
get: () => plugin.myPublicProp1 | |
}, | |
myPublicProp2: { | |
get: () => plugin.myPublicProp2 | |
} | |
}); | |
Object.seal(this); | |
} | |
} | |
/** | |
* A factory function forming the primary public entry point | |
* of the vendor code's API, as demonstrated in previous gists. | |
*/ | |
function factory() { ... } | |
/** | |
* A static method for registering plugins. | |
* | |
* @param {function} Plugin | |
* @return {void} | |
*/ | |
factory.use = (Plugin) => { | |
const plugin = new Plugin(); | |
// At this point, we check if the consumer provided plugin | |
// implements `IPlugin`, and if not, we reject the registration. | |
if (!(plugin instanceof IPlugin)) { | |
throw new TypeError('Plugins must implement IPlugin'); | |
} | |
// Register plugin ... | |
} | |
// As well as the factory itself, we'll want to expose IPlugin | |
// in order for consumer code to implement it. | |
export IPlugin; | |
export default factory; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment