Disclaimer: This topic's to provide some extra choices for those needing/wanting some protections from being able to access anything from anywhere while still allowing inheritance. As sometimes it completely makes sense for keeping everything public, these choices are entirely optional. Using any of them without any solid reason's just violating KISS for nothing, which should be avoided.
This topic aims to use an easy, simple and small scenario to briefly cover some patterns illustrating Javascript ES5 access modifiers and inheritance.
Bear in mind that those patterns can quickly become antipatterns if they're used without at least thinking twice.
So you're assumed to have at least a basic knowledge on writing Javascript and have written at least several easy, simple and small js files which work without nontrivial bugs.
The focus of this topic corresponds to 'Remembering' in the new version of the Bloom's Taxonomy.
Please note that the following concepts will be used:
1. Final - Functions/variables that can't be redefined after their initial definitions
2. Private - Functions/variables only accessible by the enclosed class/instance/function
3. Protected - Functions/variables only accessible by the enclosed class/instance and their subclasses/instances
4. Public - Functions/variables accessible from anywhere
5. Static - Functions/variables shared by all instances of the same class
On a side note: Strictly speaking, it's nearly impossible to always ensure an object method will always remain private/protected, as advanced programmers can, after thoroughly comprehended the object's API, reconstruct the whole object while preserving its external behaviors, even though its internal states will most likely be lost that way. However, it's such an unreasonably tedious task for nontrivial objects having nontrivial inheritance hierarchies that only truly trivial and/or valuable objects really worth such tremendous efforts. Therefore, let's just regard them as edge cases and move on.
Warmup
With all these in mind, let's get started.
Situation
With the context in place, the following patterns can finally come into play. All files implementing all these patterns, as well as their unit tests and integration tests, can be found in my Lockable-Container github.
You're highly encouraged and recommended to read the simplest thing that could possibly work first, which is demonstrated by lockableContainerKiss.js.
Wrapped Prototype
Composable Revealing Module
Parasitic Inheritance
Reversed Inheritance Hierarchy
Reversed Prototype Chain
Summary
I'm planning to open another topic to explain how these pattern works in details, which will lead to a solid understanding on using Javascript access modifers and inheritance.
That's all for now. What do you think about these patterns? What do you think about Javascript access modifiers and inheritance in general? Let's drop your 2 cents here
This topic aims to use an easy, simple and small scenario to briefly cover some patterns illustrating Javascript ES5 access modifiers and inheritance.
Bear in mind that those patterns can quickly become antipatterns if they're used without at least thinking twice.
So you're assumed to have at least a basic knowledge on writing Javascript and have written at least several easy, simple and small js files which work without nontrivial bugs.
The focus of this topic corresponds to 'Remembering' in the new version of the Bloom's Taxonomy.
Please note that the following concepts will be used:
1. Final - Functions/variables that can't be redefined after their initial definitions
2. Private - Functions/variables only accessible by the enclosed class/instance/function
3. Protected - Functions/variables only accessible by the enclosed class/instance and their subclasses/instances
4. Public - Functions/variables accessible from anywhere
5. Static - Functions/variables shared by all instances of the same class
On a side note: Strictly speaking, it's nearly impossible to always ensure an object method will always remain private/protected, as advanced programmers can, after thoroughly comprehended the object's API, reconstruct the whole object while preserving its external behaviors, even though its internal states will most likely be lost that way. However, it's such an unreasonably tedious task for nontrivial objects having nontrivial inheritance hierarchies that only truly trivial and/or valuable objects really worth such tremendous efforts. Therefore, let's just regard them as edge cases and move on.
Warmup
Recall that the only scoping instrument in Javascript is function, which goes hand in hand with closures. They're the very basis of emulating access modifiers in Javascript.
Without functions, anything's accessible from anywhere in Javascript, meaning that everything's public.
On the other hand, using the function scope lets one declare variables inside a function, making them only accessible within that function. These variables are thus local to that function, making them private.
When it comes to emulating protected in Javascript, one must first bear in mind that protected only makes sense when there's inheritance, which nearly always involve this.
Inheritance in Javascript are prototype based, which is different from almost(if not just) all the other programming languages supporting inheritance.
Nevertheless, protected in prototypical inheritance can still mean variables only accessible by the prototype defining them and all the other prototypes having that prototype as their parent.
Final in Javascript can be regarded as const in ES6. Static in Javascript can mean variables shared by all objects inheriting from the same prototype.
Without functions, anything's accessible from anywhere in Javascript, meaning that everything's public.
On the other hand, using the function scope lets one declare variables inside a function, making them only accessible within that function. These variables are thus local to that function, making them private.
When it comes to emulating protected in Javascript, one must first bear in mind that protected only makes sense when there's inheritance, which nearly always involve this.
Inheritance in Javascript are prototype based, which is different from almost(if not just) all the other programming languages supporting inheritance.
Nevertheless, protected in prototypical inheritance can still mean variables only accessible by the prototype defining them and all the other prototypes having that prototype as their parent.
Final in Javascript can be regarded as const in ES6. Static in Javascript can mean variables shared by all objects inheriting from the same prototype.
With all these in mind, let's get started.
Situation
Suppose we're to create a lockable container that can store a single object as the encapsulated contents and can lock itself to control its access.
This object's API consists of the following:
1. isLocked() - Check whether the lockable container is locked
2. lock() - Locks the lockable container
3. tryPutContents(contents) - Tries to put the contents to the lockable container
It'll succeed only if the lockable container's unlocked and empty, otherwise it'll show the reasons of failure
4. tryTakeContents - Tries to take the contents inside the lockable container
If it succeeds, the lockable container will become empty
It'll succeed only if the lockable container's unlocked and not empty, otherwise it'll show the reasons of failure
5. tryUnlock(key) - Tries to unlock the lockable container
It'll succeed only if the key's correct, otherwise it'll show the reasons of failure
Now the lockable container's easy, simple and small, but some users might fear that eventually an outsider can pass the correct key to unlock the lockable container, take its contents, put something malicious inside, and then lock it again. The only way to know the lockable container's compromised is to inspect its contents, which can itself lead to disastrous results.
So let's create a counted lockable container, which inherits from a lockable container, that counts the number of key mismatches, and permanently locks itself if the counter reaches a preset maximum.
This object has a new method in its API:
6. keyMismatchCount - Returns the number of key mismatches
Now the counted lockable container's noticeably safer, but it has a new problem - Given enough key mismatches, it won't be able to be unlocked anymore, even with the correct key.
So let's create a resettable counted lockable container, which inherits from a counted lockable container, that lets users to reset the key mismatch count.
This object has a new method in its API:
7. tryResetKeyMismatchCount - Tries to reset the key mismatch count to 0
It'll succeed only if the lockable container's unlocked, otherwise it'll show the reasons of failure
Now the resettable counted lockable container's also noticeably safer from being not being able to be unlocked even with the correct key, but some users might just want the reset to take place automatically whenever becomes unlocked.
So let's create a resetting counted lockable container, which inherits from a counted lockable container, that automatically resets the key mismatch count to 0 whenever it becomes unlocked.
This object's API consists of the following:
1. isLocked() - Check whether the lockable container is locked
2. lock() - Locks the lockable container
3. tryPutContents(contents) - Tries to put the contents to the lockable container
It'll succeed only if the lockable container's unlocked and empty, otherwise it'll show the reasons of failure
4. tryTakeContents - Tries to take the contents inside the lockable container
If it succeeds, the lockable container will become empty
It'll succeed only if the lockable container's unlocked and not empty, otherwise it'll show the reasons of failure
5. tryUnlock(key) - Tries to unlock the lockable container
It'll succeed only if the key's correct, otherwise it'll show the reasons of failure
Now the lockable container's easy, simple and small, but some users might fear that eventually an outsider can pass the correct key to unlock the lockable container, take its contents, put something malicious inside, and then lock it again. The only way to know the lockable container's compromised is to inspect its contents, which can itself lead to disastrous results.
So let's create a counted lockable container, which inherits from a lockable container, that counts the number of key mismatches, and permanently locks itself if the counter reaches a preset maximum.
This object has a new method in its API:
6. keyMismatchCount - Returns the number of key mismatches
Now the counted lockable container's noticeably safer, but it has a new problem - Given enough key mismatches, it won't be able to be unlocked anymore, even with the correct key.
So let's create a resettable counted lockable container, which inherits from a counted lockable container, that lets users to reset the key mismatch count.
This object has a new method in its API:
7. tryResetKeyMismatchCount - Tries to reset the key mismatch count to 0
It'll succeed only if the lockable container's unlocked, otherwise it'll show the reasons of failure
Now the resettable counted lockable container's also noticeably safer from being not being able to be unlocked even with the correct key, but some users might just want the reset to take place automatically whenever becomes unlocked.
So let's create a resetting counted lockable container, which inherits from a counted lockable container, that automatically resets the key mismatch count to 0 whenever it becomes unlocked.
With the context in place, the following patterns can finally come into play. All files implementing all these patterns, as well as their unit tests and integration tests, can be found in my Lockable-Container github.
You're highly encouraged and recommended to read the simplest thing that could possibly work first, which is demonstrated by lockableContainerKiss.js.
Wrapped Prototype
It's demonstrated by the following js files:
1. lockableContainerPrototype.js
2. countedLockableContainerPrototype.js
3. resettableCountedLockableContainerPrototype.js
4. resettingCountedLockableContainerPrototype.js
This pattern simply declares the function in the global scope, and everything else, including its prototype, inside an immediately-invoked function expression.
Note that:
1. Anything declared inside the anonymous function but not in the prototype's effectively private static final.
2. This pattern can lead to some duplicated private static constants if it's better than making them instance variables.
3. This pattern doesn't provide private instance variable nor protected function/variable.
4. This pattern causes every subclass to choose its direct parent but not vice versa.
This pattern also generally leads to extremely fast object creation codes using very little memory, and it's very scalable in terms of performance when it comes to extending the prototype chain. It's shown by the below benchmark:
So this pattern can be desirable when:
1. You don't need/want private/protected instance variables.
2. You don't need/want protected function.
3. You don't mind having duplicated private static constants.
4. You need/want extremely performant object creation codes that scale very well.
5. You need/want/don't mind every subclass to choose its direct parent but not vice versa.
In the case of the lockable container, this pattern doesn't even try to stop outsiders from directly accessing its internals, thus letting them accessing its correct key and contents, making the lockable container meaningless, pointless and useless. This's proved by the integration test as this pattern doesn't pass it. Therefore, this pattern isn't suitable for its implementations at all.
1. lockableContainerPrototype.js
2. countedLockableContainerPrototype.js
3. resettableCountedLockableContainerPrototype.js
4. resettingCountedLockableContainerPrototype.js
This pattern simply declares the function in the global scope, and everything else, including its prototype, inside an immediately-invoked function expression.
Note that:
1. Anything declared inside the anonymous function but not in the prototype's effectively private static final.
2. This pattern can lead to some duplicated private static constants if it's better than making them instance variables.
3. This pattern doesn't provide private instance variable nor protected function/variable.
4. This pattern causes every subclass to choose its direct parent but not vice versa.
This pattern also generally leads to extremely fast object creation codes using very little memory, and it's very scalable in terms of performance when it comes to extending the prototype chain. It's shown by the below benchmark:
Code:
// All performance tests are done in Google Chrome 56.0.2924.87 and i3-2330M
// Base: Roughly 7MB
// MOE: < 1MB; < 1 sec
// Total: Roughly 25MB for 100,000 times
memoryTest.push(new LockableContainer(_key1));
memoryTest.push(new LockableContainer(_key2));
//
// Total: Roughly 25MB for 100,000 times
memoryTest.push(new ResettableCountedLockableContainer(_key1,
_maxKeyMismatchCount1));
memoryTest.push(new ResettableCountedLockableContainer(_key2,
_maxKeyMismatchCount2));
//
// Total: Roughly 25MB for 100,000 times
memoryTest.push(new ResettingCountedLockableContainer(_key1,
_maxKeyMismatchCount1));
memoryTest.push(new ResettingCountedLockableContainer(_key2,
_maxKeyMismatchCount2));
//
// Roughly 15 seconds for 100,000,000 times
new LockableContainer(_key1);
new LockableContainer(_key2);
//
// Roughly 17 seconds for 100,000,000 times
new ResettableCountedLockableContainer(_key1, _maxKeyMismatchCount1);
new ResettableCountedLockableContainer(_key2, _maxKeyMismatchCount2);
//
// Roughly 17 seconds for 100,000,000 times
new ResettingCountedLockableContainer(_key1, _maxKeyMismatchCount1);
new ResettingCountedLockableContainer(_key2, _maxKeyMismatchCount2);
//
So this pattern can be desirable when:
1. You don't need/want private/protected instance variables.
2. You don't need/want protected function.
3. You don't mind having duplicated private static constants.
4. You need/want extremely performant object creation codes that scale very well.
5. You need/want/don't mind every subclass to choose its direct parent but not vice versa.
In the case of the lockable container, this pattern doesn't even try to stop outsiders from directly accessing its internals, thus letting them accessing its correct key and contents, making the lockable container meaningless, pointless and useless. This's proved by the integration test as this pattern doesn't pass it. Therefore, this pattern isn't suitable for its implementations at all.
Composable Revealing Module
It's demonstrated by the following js files:
1. lockableContainerFunction.js
2. immutableResettableCountedLockableContainerFunction.js
3. mutableResettableCountedLockableContainerFunction.js
This pattern simply wraps everything into a named function that's called rather than instantiated.
Note that:
1. Anything not returned by the function's private.
2. This pattern can lead to some duplicated private functions/variables.
3. This pattern doesn't provide protected functions/variables.
4. This pattern uses the decorator pattern to extend objects.
5. There's no static function/variable at all.
This pattern generally leads to quite some fast object creation codes when thew object's not extended but those codes uses quite a lot of memory, and it's not scalable at all in terms of performance when the object's extended. It's shown by the below benchmark:
Also, this pattern uses composition instead of inheritance to extend objects. As both a resettable counted lockable container and resetting counted lockable container needs to write the private/protected instance variable counting the number of key mismatches in a counted lockable container, the functionalities of the formers need to be included by the latter in order to extend objects only based on their APIs.
So this pattern can be desirable when:
1. You don't need/want protected function/variables.
2. You don't mind having duplicated private functions/variables.
3. You don't need/want to extend the objects at all or you don't mind suffering from poor scalability.
4. You need/want extremely fast object creation codes, even at the cost of high memory usage, and you don't need/want to extend the objects at all.
5. You need/want private functions/variables.
6. You favor composition over inheritance.
In the case of the lockable container, this pattern can effectively, efficiently and reliably stop outsiders from directly accessing its internals, at least when they're not using tons of ridiculously insane hacks.
Unless exceptionally large amount of lockable containers can exist at the same time, this pattern should be fine.
1. lockableContainerFunction.js
2. immutableResettableCountedLockableContainerFunction.js
3. mutableResettableCountedLockableContainerFunction.js
This pattern simply wraps everything into a named function that's called rather than instantiated.
Note that:
1. Anything not returned by the function's private.
2. This pattern can lead to some duplicated private functions/variables.
3. This pattern doesn't provide protected functions/variables.
4. This pattern uses the decorator pattern to extend objects.
5. There's no static function/variable at all.
This pattern generally leads to quite some fast object creation codes when thew object's not extended but those codes uses quite a lot of memory, and it's not scalable at all in terms of performance when the object's extended. It's shown by the below benchmark:
Code:
// All performance tests are done in Google Chrome 56.0.2924.87 and i3-2330M
// Base: Roughly 7MB
// MOE: < 1MB; < 1 sec
// Total: Roughly 151MB for 100,000 times
memoryTest.push(LockableContainerFunction(_key1));
memoryTest.push(LockableContainerFunction(_key2));
//
// Total: Roughly 240MB for 100,000 times
memoryTest.push(MutableResettableCountedLockableContainerFunction(
LockableContainerFunction(_key1), _maxKeyMismatchCount1, true,
false));
memoryTest.push(MutableResettableCountedLockableContainerFunction(
LockableContainerFunction(_key2), _maxKeyMismatchCount2, false,
true));
//
// Total: Roughly 265MB for 100,000 times
memoryTest.push(ImmutableResettableCountedLockableContainerFunction(
LockableContainerFunction(_key1), _maxKeyMismatchCount1, true,
false));
memoryTest.push(ImmutableResettableCountedLockableContainerFunction(
LockableContainerFunction(_key2), _maxKeyMismatchCount2, false,
true));
//
// Roughly 48 seconds for 100,000,000 times
LockableContainerFunction(_key1);
LockableContainerFunction(_key2);
//
// Roughly 91 seconds for 100,000,000 times
ImmutableResettableCountedLockableContainerFunction(
LockableContainerFunction(_key1), _maxKeyMismatchCount1,
isResettable, isResetting);
ImmutableResettableCountedLockableContainerFunction(
LockableContainerFunction(_key2), _maxKeyMismatchCount2,
isResettable, isResetting);
//
// Roughly 17 seconds for 1,000,000 times
MutableResettableCountedLockableContainerFunction(
LockableContainerFunction(_key1), _maxKeyMismatchCount1,
isResettable, isResetting);
MutableResettableCountedLockableContainerFunction(
LockableContainerFunction(_key2), _maxKeyMismatchCount2,
isResettable, isResetting);
//
Also, this pattern uses composition instead of inheritance to extend objects. As both a resettable counted lockable container and resetting counted lockable container needs to write the private/protected instance variable counting the number of key mismatches in a counted lockable container, the functionalities of the formers need to be included by the latter in order to extend objects only based on their APIs.
So this pattern can be desirable when:
1. You don't need/want protected function/variables.
2. You don't mind having duplicated private functions/variables.
3. You don't need/want to extend the objects at all or you don't mind suffering from poor scalability.
4. You need/want extremely fast object creation codes, even at the cost of high memory usage, and you don't need/want to extend the objects at all.
5. You need/want private functions/variables.
6. You favor composition over inheritance.
In the case of the lockable container, this pattern can effectively, efficiently and reliably stop outsiders from directly accessing its internals, at least when they're not using tons of ridiculously insane hacks.
Unless exceptionally large amount of lockable containers can exist at the same time, this pattern should be fine.
Parasitic Inheritance
It's demonstrated by the following js files:
1. lockableContainerClass.js
2. countedLockableContainerClass.js
3. resettablecountedLockableContainerClass.js
4. resettingcountedLockableContainerClass.js
This pattern simply wraps everything into a named function that's called by subclasses and instantiated to create new objects.
Note that:
1. Anything not attached to the function's this pointer's private.
2. This pattern can lead to some duplicated private functions/variables.
3. This pattern provides loosely but not strictly protected functions/variables.
4. There's no static function/variable at all.
5. This pattern causes every subclass to choose its direct parent but not vice versa.
6. This pattern supports multiple inheritance, albeit with the diamond problem in the way that the last inherited parent dominates.
7. Subclasses can expose the protected functions/variables into public ones by using accessors.
This pattern generally leads to considerably slow codes that uses quite some memory, and it's not that scalable in terms of performance when the object's extended. It's shown by the below benchmark:
Moreover, the protected functions/variables in this pattern can be compromised without too much troubles, as demonstrated by these files:
1. unprotectedLockableContainerClass.js
2. unprotectedCountedLockableContainerClass.js
A serious side effect is that the integrity of the whole object can be compromised, causing it to fail to work correctly.
In this case, such a jailbreak causes the compromised object to use the new versions of the protected functions/variables, while the original codes are still using their old versions.
That's why this pattern doesn't provide strictly protected functions/variables, only loosely protected ones, as it doesn't stop them to be compromised while still letting them express themselves and function as protected ones as long as they're not compromised.
So this pattern can be desirable when:
1. You need/want private and protected functions/variables.
2. You don't mind having duplicated private constants.
3. You don't mind extremely unperformant object creation codes that doesn't scale well.
4. You need/want/don't mind every subclass to choose its direct parent but not vice versa.
5. You don't mind having protected functions/variables compromised, which can compromise the integrity of the whole object as well.
6. You need/want multiple inheritance.
7. You need/want/don't mind subclasses turning the protected functions/variables into public ones.
In the case of the lockable container, whether this pattern's desirable 's mainly determined by the risk of the objects being compromised, which depends on actual use cases.
1. lockableContainerClass.js
2. countedLockableContainerClass.js
3. resettablecountedLockableContainerClass.js
4. resettingcountedLockableContainerClass.js
This pattern simply wraps everything into a named function that's called by subclasses and instantiated to create new objects.
Note that:
1. Anything not attached to the function's this pointer's private.
2. This pattern can lead to some duplicated private functions/variables.
3. This pattern provides loosely but not strictly protected functions/variables.
4. There's no static function/variable at all.
5. This pattern causes every subclass to choose its direct parent but not vice versa.
6. This pattern supports multiple inheritance, albeit with the diamond problem in the way that the last inherited parent dominates.
7. Subclasses can expose the protected functions/variables into public ones by using accessors.
This pattern generally leads to considerably slow codes that uses quite some memory, and it's not that scalable in terms of performance when the object's extended. It's shown by the below benchmark:
Code:
// All performance tests are done in Google Chrome 56.0.2924.87 and i3-2330M
// Base: Roughly 7MB
// MOE: < 1MB; < 1 sec
// Total: Roughly 116MB for 100,000 times
memoryTest.push(new LockableContainerClass(_key1,
_maxKeyMismatchCount1));
memoryTest.push(new LockableContainerClass(_key2,
_maxKeyMismatchCount2));
//
// Total: Roughly 217MB for 100,000 times
memoryTest.push(new ResettableCountedLockableContainerClass(_key1,
_maxKeyMismatchCount1));
memoryTest.push(new ResettableCountedLockableContainerClass(_key2,
_maxKeyMismatchCount2));
//
// Total: Roughly 212MB for 100,000 times
memoryTest.push(new ResettingCountedLockableContainerClass(_key1,
_maxKeyMismatchCount1));
memoryTest.push(new ResettingCountedLockableContainerClass(_key2,
_maxKeyMismatchCount2));
//
// Roughly 74 seconds for 10,000,000 times
new LockableContainerClass(_key1);
new LockableContainerClass(_key2);
//
// Roughly 12 seconds for 1,000,000 times
new ResettableCountedLockableContainerClass(_key1,
_maxKeyMismatchCount1);
new ResettableCountedLockableContainerClass(_key2,
_maxKeyMismatchCount2);
//
// Roughly 13 seconds for 1,000,000 times
new ResettingCountedLockableContainerClass(_key1,
_maxKeyMismatchCount1);
new ResettingCountedLockableContainerClass(_key2,
_maxKeyMismatchCount2);
//
Moreover, the protected functions/variables in this pattern can be compromised without too much troubles, as demonstrated by these files:
1. unprotectedLockableContainerClass.js
2. unprotectedCountedLockableContainerClass.js
A serious side effect is that the integrity of the whole object can be compromised, causing it to fail to work correctly.
In this case, such a jailbreak causes the compromised object to use the new versions of the protected functions/variables, while the original codes are still using their old versions.
That's why this pattern doesn't provide strictly protected functions/variables, only loosely protected ones, as it doesn't stop them to be compromised while still letting them express themselves and function as protected ones as long as they're not compromised.
So this pattern can be desirable when:
1. You need/want private and protected functions/variables.
2. You don't mind having duplicated private constants.
3. You don't mind extremely unperformant object creation codes that doesn't scale well.
4. You need/want/don't mind every subclass to choose its direct parent but not vice versa.
5. You don't mind having protected functions/variables compromised, which can compromise the integrity of the whole object as well.
6. You need/want multiple inheritance.
7. You need/want/don't mind subclasses turning the protected functions/variables into public ones.
In the case of the lockable container, whether this pattern's desirable 's mainly determined by the risk of the objects being compromised, which depends on actual use cases.
Reversed Inheritance Hierarchy
It's demonstrated by the following js files:
1. lockableContainerObject.js
2. countedLockableContainerObject.js
3. resettableCountedLockableContainerObject.js
4. resettingCountedLockableContainerObject.js
This pattern simply wraps everything into a named function, including the declaration of all subclasses, that's instantiated to create new objects.
Note that:
1. Anything not attached to the function's this pointer's private.
2. This pattern can lead to some duplicated private functions/variables.
3. This pattern provides strictly protected functions/variables, which are those attached to the function's this pointer but not returned by the function.
4. There's no static function/variable at all.
5. This pattern causes every parent to choose all its subclasses but not vice versa.
This pattern generally leads to exceptionally slow object creation codes that uses a stunning amount of memory, and it's rather not scalable in terms of performance when the object's extended. It's shown by the below benchmark:
Besides, the control in the inheritance hierarchy's reversed - Rather than letting subclasses to control which classes are their parents, this pattern lets parents to control which are their subclasses. This has the following implications:
1. In the normal inheritance hierarchy, anyone can create subclasses for any public class meant to be inherited; This pattern only lets foreign classes authorized by the classes in this pattern to be the latters' subclasses.
2. In the normal inheritance hierarchy, a class's protected functions/variables can risk being public if it's subclassed by classes exposing them; This pattern can prevent this from happening by strictly controlling the external behaviors of all subclasses.
3. If no one has access to the source code of a class using this pattern, it won't be able to be subclassed by any new class anymore.
4. This pattern isn't maintainable when the inheritance hierarchy becomes colossal, complex and convoluted.
So this pattern can be desirable when:
1. You need/want private and protected functions/variables.
2. You don't mind having duplicated private constants.
3. You don't mind having excessively unperformant object creation codes that lacks scalability.
4. You need/want/don't mind having every parent to choose all its subclasses but not vice versa.
5. You don't mind leading to effectively final classes when no one has access to their source codes anymore.
6. You don't mind managing an unmaintainable mess with a colossal, complex and convoluted inheritance hierarchy.
In the case of the lockable container, this pattern can effectively, efficiently and reliably stop outsiders from directly accessing its internals, at least when they're not using tons of ridiculously insane hacks.
Unless exceptionally large amount of lockable containers can exist at the same time, this pattern should be fine.
1. lockableContainerObject.js
2. countedLockableContainerObject.js
3. resettableCountedLockableContainerObject.js
4. resettingCountedLockableContainerObject.js
This pattern simply wraps everything into a named function, including the declaration of all subclasses, that's instantiated to create new objects.
Note that:
1. Anything not attached to the function's this pointer's private.
2. This pattern can lead to some duplicated private functions/variables.
3. This pattern provides strictly protected functions/variables, which are those attached to the function's this pointer but not returned by the function.
4. There's no static function/variable at all.
5. This pattern causes every parent to choose all its subclasses but not vice versa.
This pattern generally leads to exceptionally slow object creation codes that uses a stunning amount of memory, and it's rather not scalable in terms of performance when the object's extended. It's shown by the below benchmark:
Code:
// All performance tests are done in Google Chrome 56.0.2924.87 and i3-2330M
// Base: Roughly 7MB
// MOE: < 1MB; < 1 sec
// Total: Roughly 210MB for 100,000 times
memoryTest.push(new LockableContainerObject('LockableContainer',
_key1));
memoryTest.push(new LockableContainerObject('LockableContainer',
_key2));
//
// Total: Roughly 336MB for 100,000 times
memoryTest.push(new LockableContainerObject(
'ResettableCountedLockableContainer', _key1,
_maxKeyMismatchCount1));
memoryTest.push(new LockableContainerObject(
'ResettableCountedLockableContainer', _key2,
_maxKeyMismatchCount2));
//
// Total: Roughly 329MB for 100,000 times
memoryTest.push(new LockableContainerObject(
'ResettingCountedLockableContainer', _key1,
_maxKeyMismatchCount1));
memoryTest.push(new LockableContainerObject(
'ResettingCountedLockableContainer', _key2,
_maxKeyMismatchCount2));
//
// Roughly 17 seconds for 1,000,000 times
new LockableContainerObject('LockableContainer', _key1);
new LockableContainerObject('LockableContainer', _key2);
//
// Roughly 31 seconds for 1,000,000 times
new LockableContainerObject('ResettableCountedLockableContainer',
_key1, _maxKeyMismatchCount1);
new LockableContainerObject('ResettableCountedLockableContainer',
_key2, _maxKeyMismatchCount2);
//
// Roughly 35 seconds for 1,000,000 times
new LockableContainerObject('ResettingCountedLockableContainer',
_key1, _maxKeyMismatchCount1);
new LockableContainerObject('ResettingCountedLockableContainer',
_key2, _maxKeyMismatchCount2);
//
Besides, the control in the inheritance hierarchy's reversed - Rather than letting subclasses to control which classes are their parents, this pattern lets parents to control which are their subclasses. This has the following implications:
1. In the normal inheritance hierarchy, anyone can create subclasses for any public class meant to be inherited; This pattern only lets foreign classes authorized by the classes in this pattern to be the latters' subclasses.
2. In the normal inheritance hierarchy, a class's protected functions/variables can risk being public if it's subclassed by classes exposing them; This pattern can prevent this from happening by strictly controlling the external behaviors of all subclasses.
3. If no one has access to the source code of a class using this pattern, it won't be able to be subclassed by any new class anymore.
4. This pattern isn't maintainable when the inheritance hierarchy becomes colossal, complex and convoluted.
So this pattern can be desirable when:
1. You need/want private and protected functions/variables.
2. You don't mind having duplicated private constants.
3. You don't mind having excessively unperformant object creation codes that lacks scalability.
4. You need/want/don't mind having every parent to choose all its subclasses but not vice versa.
5. You don't mind leading to effectively final classes when no one has access to their source codes anymore.
6. You don't mind managing an unmaintainable mess with a colossal, complex and convoluted inheritance hierarchy.
In the case of the lockable container, this pattern can effectively, efficiently and reliably stop outsiders from directly accessing its internals, at least when they're not using tons of ridiculously insane hacks.
Unless exceptionally large amount of lockable containers can exist at the same time, this pattern should be fine.
Reversed Prototype Chain
It's demonstrated by the following js files:
1. lockableContainerFactory.js
2. countedLockableContainerFactory.js
3. resettableCountedLockableContainerFactory.js
4. resettingCountedLockableContainerFactory.js
This pattern simply wraps everything into a named function, including the declaration of all subclasses and defintion of the prototype for creating objects, that's called once to return a function creating objects.
Note that:
1. Anything not attached to the prototype's private static final function/variable.
2. This pattern can lead to some duplicated private static final functions/variables.
3. This pattern doesn't provide private instance variables.
4. This pattern provides strictly protected functions/variables, which are those attached to the function's this pointer but not returned by the function.
5. This pattern causes every parent to choose all its subclasses but not vice versa.
6. This pattern is largely based on the factory method pattern.
7. The prototype's created only once if this pattern's used properly.
This pattern generally leads to slightly slow object creation codes that uses an acceptable amount of memory, and it's reasonably scalable in terms of performance when the object's extended. It's shown by the below benchmark:
Similar to the Reversed Inheritance Hierarchy, the control in the inheritance hierarchy's reversed in this pattern as well, leading to the same implications.
So this pattern can be desirable when:
1. You need/want protected functions/variables.
2. You don't need/want private instance variables.
3. You don't mind having duplicated private constants.
4. You don't mind having slightly unperformant object creation codes that's decently scalable.
5. You need/want/don't mind having every parent to choose all its subclasses but not vice versa.
6. You don't mind leading to effectively final classes when no one has access to their source codes anymore.
7. You don't mind managing an unmaintainable mess with a colossal, complex and convoluted inheritance hierarchy.
In the case of the lockable container, this pattern can effectively, efficiently and reliably stop outsiders from directly accessing its internals, at least when they're not using tons of ridiculously insane hacks.
Unless unbelivably large amount of lockable containers can exist at the same time, this pattern should be fine.
1. lockableContainerFactory.js
2. countedLockableContainerFactory.js
3. resettableCountedLockableContainerFactory.js
4. resettingCountedLockableContainerFactory.js
This pattern simply wraps everything into a named function, including the declaration of all subclasses and defintion of the prototype for creating objects, that's called once to return a function creating objects.
Note that:
1. Anything not attached to the prototype's private static final function/variable.
2. This pattern can lead to some duplicated private static final functions/variables.
3. This pattern doesn't provide private instance variables.
4. This pattern provides strictly protected functions/variables, which are those attached to the function's this pointer but not returned by the function.
5. This pattern causes every parent to choose all its subclasses but not vice versa.
6. This pattern is largely based on the factory method pattern.
7. The prototype's created only once if this pattern's used properly.
This pattern generally leads to slightly slow object creation codes that uses an acceptable amount of memory, and it's reasonably scalable in terms of performance when the object's extended. It's shown by the below benchmark:
Code:
// All performance tests are done in Google Chrome 56.0.2924.87 and i3-2330M
// Base: Roughly 7MB
// MOE: < 1MB; < 1 sec
// Total: Roughly 60MB for 100,000 times
memoryTest.push(_createdLockableContainerFactory('LockableContainer',
_key1));
memoryTest.push(_createdLockableContainerFactory('LockableContainer',
_key2));
//
// Total: Roughly 71MB for 100,000 times
memoryTest.push(_createdLockableContainerFactory(
'ResettableCountedLockableContainer', _key1,
_maxKeyMismatchCount1));
memoryTest.push(_createdLockableContainerFactory(
'ResettableCountedLockableContainer', _key2,
_maxKeyMismatchCount2));
//
// Total: Roughly 67MB for 100,000 times
memoryTest.push(_createdLockableContainerFactory(
'ResettingCountedLockableContainer', _key1,
_maxKeyMismatchCount1));
memoryTest.push(_createdLockableContainerFactory(
'ResettingCountedLockableContainer', _key2,
_maxKeyMismatchCount2));
//
// Roughly 21 seconds for 10,000,000 times
_createdLockableContainerFactory('LockableContainer', _key1);
_createdLockableContainerFactory('LockableContainer', _key2);
//
// Roughly 24 seconds for 10,000,000 times
_createdLockableContainerFactory('ResettableCountedLockableContainer',
_key1, _maxKeyMismatchCount1);
_createdLockableContainerFactory('ResettableCountedLockableContainer',
_key2, _maxKeyMismatchCount2);
//
// Roughly 25 seconds for 10,000,000 times
_createdLockableContainerFactory('ResettingCountedLockableContainer',
_key1, _maxKeyMismatchCount1);
_createdLockableContainerFactory('ResettingCountedLockableContainer',
_key2, _maxKeyMismatchCount2);
//
Similar to the Reversed Inheritance Hierarchy, the control in the inheritance hierarchy's reversed in this pattern as well, leading to the same implications.
So this pattern can be desirable when:
1. You need/want protected functions/variables.
2. You don't need/want private instance variables.
3. You don't mind having duplicated private constants.
4. You don't mind having slightly unperformant object creation codes that's decently scalable.
5. You need/want/don't mind having every parent to choose all its subclasses but not vice versa.
6. You don't mind leading to effectively final classes when no one has access to their source codes anymore.
7. You don't mind managing an unmaintainable mess with a colossal, complex and convoluted inheritance hierarchy.
In the case of the lockable container, this pattern can effectively, efficiently and reliably stop outsiders from directly accessing its internals, at least when they're not using tons of ridiculously insane hacks.
Unless unbelivably large amount of lockable containers can exist at the same time, this pattern should be fine.
Summary
Wrapped Prototype simply declares the function in the global scope, and everything else, including its prototype, inside an immediately-invoked function expression.
This pattern can be desirable when:
1. You don't need/want private/protected instance variables.
2. You don't need/want protected function.
3. You don't mind having duplicated private static constants.
4. You need/want extremely performant object creation codes that scale very well.
5. You need/want/don't mind every subclass to choose its direct parent but not vice versa.
Composable Revealing Module simply wraps everything into a named function that's called rather than instantiated.
This pattern can be desirable when:
1. You don't need/want protected function/variables.
2. You don't mind having duplicated private functions/variables.
3. You don't need/want to extend the objects at all or you don't mind suffering from poor scalability.
4. You need/want extremely fast object creation codes, even at the cost of high memory usage, and you don't need/want to extend the objects at all.
5. You need/want private functions/variables.
6. You favor composition over inheritance.
Parasitic Inheritance simply wraps everything into a named function that's called by subclasses and instantiated to create new objects.
This pattern can be desirable when:
1. You need/want private and protected functions/variables.
2. You don't mind having duplicated private constants.
3. You don't mind extremely unperformant object creation codes that doesn't scale well.
4. You need/want/don't mind every subclass to choose its direct parent but not vice versa.
5. You don't mind having protected functions/variables compromised, which can compromise the integrity of the whole object as well.
6. You need/want multiple inheritance.
7. You need/want/don't mind subclasses turning the protected functions/variables into public ones.
Reversed Inheritance Hierarchy simply wraps everything into a named function, including the declaration of all subclasses, that's instantiated to create new objects.
This pattern can be desirable when:
1. You need/want private and protected functions/variables.
2. You don't mind having duplicated private constants.
3. You don't mind having excessively unperformant object creation codes that lacks scalability.
4. You need/want/don't mind having every parent to choose all its subclasses but not vice versa.
5. You don't mind leading to effectively final classes when no one has access to their source codes anymore.
6. You don't mind managing an unmaintainable mess with a colossal, complex and convoluted inheritance hierarchy.
Reversed Prototype Chain simply wraps everything into a named function, including the declaration of all subclasses and defintion of the prototype for creating objects, that's called once to return a function creating objects.
This pattern can be desirable when:
1. You need/want protected functions/variables.
2. You don't need/want private instance variables.
3. You don't mind having duplicated private constants.
4. You don't mind having slightly unperformant object creation codes that's decently scalable.
5. You need/want/don't mind having every parent to choose all its subclasses but not vice versa.
6. You don't mind leading to effectively final classes when no one has access to their source codes anymore.
7. You don't mind managing an unmaintainable mess with a colossal, complex and convoluted inheritance hierarchy.
This pattern can be desirable when:
1. You don't need/want private/protected instance variables.
2. You don't need/want protected function.
3. You don't mind having duplicated private static constants.
4. You need/want extremely performant object creation codes that scale very well.
5. You need/want/don't mind every subclass to choose its direct parent but not vice versa.
Composable Revealing Module simply wraps everything into a named function that's called rather than instantiated.
This pattern can be desirable when:
1. You don't need/want protected function/variables.
2. You don't mind having duplicated private functions/variables.
3. You don't need/want to extend the objects at all or you don't mind suffering from poor scalability.
4. You need/want extremely fast object creation codes, even at the cost of high memory usage, and you don't need/want to extend the objects at all.
5. You need/want private functions/variables.
6. You favor composition over inheritance.
Parasitic Inheritance simply wraps everything into a named function that's called by subclasses and instantiated to create new objects.
This pattern can be desirable when:
1. You need/want private and protected functions/variables.
2. You don't mind having duplicated private constants.
3. You don't mind extremely unperformant object creation codes that doesn't scale well.
4. You need/want/don't mind every subclass to choose its direct parent but not vice versa.
5. You don't mind having protected functions/variables compromised, which can compromise the integrity of the whole object as well.
6. You need/want multiple inheritance.
7. You need/want/don't mind subclasses turning the protected functions/variables into public ones.
Reversed Inheritance Hierarchy simply wraps everything into a named function, including the declaration of all subclasses, that's instantiated to create new objects.
This pattern can be desirable when:
1. You need/want private and protected functions/variables.
2. You don't mind having duplicated private constants.
3. You don't mind having excessively unperformant object creation codes that lacks scalability.
4. You need/want/don't mind having every parent to choose all its subclasses but not vice versa.
5. You don't mind leading to effectively final classes when no one has access to their source codes anymore.
6. You don't mind managing an unmaintainable mess with a colossal, complex and convoluted inheritance hierarchy.
Reversed Prototype Chain simply wraps everything into a named function, including the declaration of all subclasses and defintion of the prototype for creating objects, that's called once to return a function creating objects.
This pattern can be desirable when:
1. You need/want protected functions/variables.
2. You don't need/want private instance variables.
3. You don't mind having duplicated private constants.
4. You don't mind having slightly unperformant object creation codes that's decently scalable.
5. You need/want/don't mind having every parent to choose all its subclasses but not vice versa.
6. You don't mind leading to effectively final classes when no one has access to their source codes anymore.
7. You don't mind managing an unmaintainable mess with a colossal, complex and convoluted inheritance hierarchy.
I'm planning to open another topic to explain how these pattern works in details, which will lead to a solid understanding on using Javascript access modifers and inheritance.
That's all for now. What do you think about these patterns? What do you think about Javascript access modifiers and inheritance in general? Let's drop your 2 cents here