From 64ffefe6779635e8a48586daaaad594e4d246493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Marie=20De=20Mey?= Date: Sat, 2 Nov 2024 09:44:04 +0200 Subject: [PATCH] protect->seclude --- README.md | 28 ++++++++++++------------- src/helpers.ts | 21 +++++++++++++++++++ src/index.ts | 2 +- src/{protect.ts => seclude.ts} | 26 +++++++++++------------ test/dynamic.test.ts | 4 ---- test/protect.test.ts | 8 +++---- test/utils.test.ts | 38 ++++++++++++++++++++++++++++++++++ 7 files changed, 91 insertions(+), 36 deletions(-) rename src/{protect.ts => seclude.ts} (85%) create mode 100644 test/utils.test.ts diff --git a/README.md b/README.md index 179d542..224165b 100644 --- a/README.md +++ b/README.md @@ -213,7 +213,7 @@ import D, { constructedObject } from 'flat-diamond' > Note: There is no need to modify it directly, all the properties initialized on the temporary object are going to be transposed on it > Note: `constructedObject(this)` will return a relevant value _only_ in classes extending `Diamond(...)` after `super(...)` -# Protection +# Seclusion Another big deal of diamond inheritance is field conflicts. @@ -225,50 +225,50 @@ Don't make field conflicts. Just don't. ## Yes, but it's a library I don't write -Here it is tricky, and that's where _protection_ comes in. Let's speak about protection without speaking of diamond - and, if you wish, the protection works without the need of involving `Diamond`. (though it is also completely integrated) +Here it is tricky, and that's where _seclusion_ comes in. Let's speak about seclusion without speaking of diamond - and, if you wish, the seclusion works without the need of involving `Diamond`. (though it is also completely integrated) Let's say we want a `DuckCourier` to implement `Plane`, and end up with a conflict of `wingSpan` (the one of the duck and the one of the device strapped on him, the `Plane` one) -A pure and simple `class DuckCourier extends Plane` would have a field conflict. So, instead, protection will be used : +A pure and simple `class DuckCourier extends Plane` would have a field conflict. So, instead, seclusion will be used : ```ts -import { Protect } from 'flat-diamond' +import { Seclude } from 'flat-diamond' -class DuckCourier extends Protect(Plane, ['wingSpan']) { ... } +class DuckCourier extends Seclude(Plane, ['wingSpan']) { ... } ``` As simple as that, methods (as well as accessors) of `Plane` and `DuckCourier` will access two different values when accessing `this.wingSpan` ## But ... How ? And, how can I ... -When a protected class is implemented, `this` (so, here, a `DuckCourier`) will be used as the prototype for a `Private`. A Proxy is added between `Protected` and `Plane` to manage who is `this` in method calls (either `DuckCourier` or `Private`) - et voilĂ ! +When a secluded class is implemented, `this` (so, here, a `DuckCourier`) will be used as the prototype for a `Private`. A Proxy is added between `Secluded` and `Plane` to manage who is `this` in method calls (either `DuckCourier` or `Private`) - et voilĂ ! -Because of prototyping, `Private` has access to all the functionalities of `DuckCourier` (and therefore of `Plane`) while never interfering with `DuckCourier::wingSpan`. Also, having several protected class in the legacy list will only create several "heads" who will share a prototype. +Because of prototyping, `Private` has access to all the functionalities of `DuckCourier` (and therefore of `Plane`) while never interfering with `DuckCourier::wingSpan`. Also, having several secluded class in the legacy list will only create several "heads" who will share a prototype. -`DuckCourier` on another hand, _can_ interfere with `Plane::wingSpan` if needed thanks to the `privatePart` exposed by the `Protected` class. +`DuckCourier` on another hand, _can_ interfere with `Plane::wingSpan` if needed thanks to the `privatePart` exposed by the `Secluded` class. ```ts -import { Protect } from 'flat-diamond' +import { Seclude } from 'flat-diamond' class Plane { wingSpan: number = 200 } -const ProtectedPlane = Protect(Plane, ['wingSpan']) +const SecludedPlane = Seclude(Plane, ['wingSpan']) -class DuckCourier extends ProtectedPlane { +class DuckCourier extends SecludedPlane { wingSpan: number = 80 get isDeviceSafe(): boolean { - return ProtectedPlane.privatePart(this).wingSpan > 2 * this.wingSpan + return SecludedPlane.privatePart(this).wingSpan > 2 * this.wingSpan } } ``` ## Limitations -Again, the object exposed in the constructor won't be the same as the one faces from inside the protected object' methods/accessors +Again, the object exposed in the constructor won't be the same as the one faces from inside the secluded object' methods/accessors -For now, only the fields can be protected, not the methods +For now, only the fields can be secluded, not the methods # Participation diff --git a/src/helpers.ts b/src/helpers.ts index 0313e87..29de7d4 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -25,3 +25,24 @@ export function instanceOf(obj: any, ctor: Ctor) { if (base === ctor || base.prototype instanceof ctor) return true return false } + +type Method = ( + this: InstanceType, + ...args: Args +) => Return + +/** + * + * @param target + * @param factory + * @deprecated Not deprecated but incomplete - just pushed away with other changes : *unreliable!* + */ +export function overrideMethods( + target: Class, + factory: (source: InstanceType) => Record> +) { + Object.defineProperties( + target.prototype, + Object.getOwnPropertyDescriptors(factory(Object.getPrototypeOf(target.prototype))) + ) +} diff --git a/src/index.ts b/src/index.ts index bfa8588..80248b4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import Diamond from './diamond' export default Diamond export * from './helpers' -export * from './protect' +export * from './seclude' export * from './types' diff --git a/src/protect.ts b/src/seclude.ts similarity index 85% rename from src/protect.ts rename to src/seclude.ts index d7fac90..563c1c0 100644 --- a/src/protect.ts +++ b/src/seclude.ts @@ -5,23 +5,23 @@ import { allFLegs, bottomLeg, fLegs, nextInLine } from './utils' const publicPart = (x: Ctor): Ctor => Object.getPrototypeOf(Object.getPrototypeOf(x)) -export type Prutected)[]> = Newable< +export type Secluded)[]> = Newable< Omit, Keys[number]> > & { privatePart(obj: InstanceType): InstanceType | undefined } -export function Protect)[]>( +export function Seclude)[]>( base: TBase, properties: Keys -): Prutected { - const protectedProperties: KeySet = properties.reduce( +): Secluded { + const secludedProperties: KeySet = properties.reduce( (acc, p) => ({ ...acc, [p]: true }) as KeySet, {} ), initPropertiesBasket: PropertyDescriptorMap[] = [] /** * In order to integrate well in diamonds, we need to be a diamond - * When we create a diamond between the Protected and the base, the private properties of the base *have to* + * When we create a diamond between the Secludeded and the base, the private properties of the base *have to* * be collected before the diamond propagate them to the `constructedObject` */ abstract class PropertyCollector extends base { @@ -29,16 +29,16 @@ export function Protect() + const privates = new WeakMap() const diamond = fLegs(base) ? PropertyCollector : (Diamond(PropertyCollector) as TBase) - class Protected extends (diamond as any) { + class Secluded extends (diamond as any) { static privatePart(obj: TBase): TBase | undefined { return privates.get(obj) } @@ -57,7 +57,7 @@ export function Protect diamond.prototype }) - Object.setPrototypeOf(Protected.prototype, fakeCtor.prototype) - return Protected as any + Object.setPrototypeOf(Secluded.prototype, fakeCtor.prototype) + return Secluded as any } diff --git a/test/dynamic.test.ts b/test/dynamic.test.ts index 6cff7ef..196c3a0 100644 --- a/test/dynamic.test.ts +++ b/test/dynamic.test.ts @@ -7,10 +7,6 @@ class A { } class B extends Diamond(A) { - constructor(...args: any[]) { - const [myArg, ...rest] = args - super(...rest) - } method(x: number) { return super.method(x + 2) } diff --git a/test/protect.test.ts b/test/protect.test.ts index f19e892..844f095 100644 --- a/test/protect.test.ts +++ b/test/protect.test.ts @@ -1,4 +1,4 @@ -import Diamond, { Protect } from '../src' +import Diamond, { Seclude } from '../src' interface Scenario { pubFld: number @@ -56,7 +56,7 @@ test('leg-less', () => { this.prvFld = v } } - const P = Protect(X, ['prvFld']) + const P = Seclude(X, ['prvFld']) class Y extends P { prvFld = 10 } @@ -88,7 +88,7 @@ test('leg-half', () => { this.prvFld = v } } - const P = Protect(X, ['prvFld']) + const P = Seclude(X, ['prvFld']) class Y { prvFld = 10 } @@ -123,7 +123,7 @@ test('leg-full', () => { this.prvFld = v } } - const P = Protect(X, ['prvFld']) + const P = Seclude(X, ['prvFld']) class Y { prvFld = 10 } diff --git a/test/utils.test.ts b/test/utils.test.ts new file mode 100644 index 0000000..07c0a01 --- /dev/null +++ b/test/utils.test.ts @@ -0,0 +1,38 @@ +import { overrideMethods } from '~/helpers' +import { log, logs } from './logger' + +beforeEach(() => { + logs() +}) +test('overrideMethods', () => { + class A { + method() { + log('A', 'method') + } + get value() { + return 42 + } + } + class B extends A { + method() { + log('B', 'method') + super.method() + } + get value() { + return 5 + super.value + } + } + const b = new B() + b.method() + expect(logs()).toEqual(['B method', 'A method']) + expect(b.value).toBe(47) + overrideMethods(B, (source) => ({ + method() { + log('B', 'modified method') + source.method() + } /* + get value() { + return 15 + source.value + }*/ + })) +})