javascript - ¿Es buena práctica encapsular los mutadores en una clase dentro de un módulo JS?
Estamos siendo lo más funcionales posible con nuestro nuevo producto usando JavaScript. Tengo un módulo de Autenticación que tiene un tokenPromise que se actualiza cada vez que el usuario inicia sesión o se actualiza el token. Parece que tenemos que permitir la mutación.
En lugar de poner tokenPromise a nivel de módulo, he creado una clase que solo contiene funciones de alto nivel que limitan cómo se puede mutar el estado. Otras funciones auxiliares que son puras (o al menos no necesitan mutar el estado) están fuera de la clase. Esto parece ayudar mucho a razonar sobre cuándo podría cambiar el miembro: está colocada junto con todas las operaciones que podrían cambiarlo.
No he encontrado otros ejemplos de un patrón así; ¿se considera esto una buena práctica, o hay otra forma en la que deberíamos estar mirando? Aquí está la clase que contiene los datos mutables, la cual se exporta desde Authentication.ts.
export default class Authentication {
public static async getAuthToken(): Promise<string> {
if (!this.tokenPromise || await hasExpired(this.tokenPromise)) {
// Either we've never fetched, or memory was cleared, or expired
this.tokenPromise = getUpdatedTokenPromise();
}
return (await this.tokenPromise).idToken;
}
public static async logOut(): Promise<void> {
this.tokenPromise = null;
await LocalStorage.clearAuthCredentials();
// Just restart to log out for now
RNRestart.Restart();
}
private static tokenPromise: Promise<IAuthToken> | null;
}
// After, at the module level, we define all helper functions that don't need to mutate this module's state - getUpdatedAuthToken(), etc.
Un posible principio parece ser: mantener los objetos con estado mutable lo más compactos posible, exponiendo solo métodos compactos de alto nivel para mutar el estado (por ejemplo, logOut y refreshAuthToken, no get/set authToken).
Respuesta #1
He creado una clase que solo contiene funciones de alto nivel que limitan cómo se puede mutar el estado. Esto parece ayudar mucho a razonar sobre cuándo el miembro podría cambiar: está colocada junto con todas las operaciones que podrían cambiarlo.
Sí, esta es una práctica recomendada estándar en la POO: la separación de preocupaciones mediante la encapsulación de los cambios de estado en el objeto. Ningún otro código fuera del objeto (la clase) puede modificarlo.
Otras funciones auxiliares que son puras (o al menos no necesitan mutar el estado) están fuera de la clase.
No iría tan lejos. También deberías poner las funciones auxiliares (¿métodos?) que pertenecen a instancias en la clase, o al menos en su proximidad directa; aunque ponerlas en el mismo módulo podría ser suficiente. Especialmente cuando acceden a partes "privadas" de los objetos. Para simplemente distinguir funciones puras de impuras, también podrías usar convenciones como prefijos get para métodos puros.
Una alternativa a eso es proporcionar una interfaz inmutable separada para tu clase que contenga solo los métodos puros. Podrías tener esto como una segunda declaración de class, y usar un método para convertir entre las representaciones.
exponiendo solo métodos compactos de alto nivel para mutar el estado
Creo que eso no es completamente cierto. También estás exponiendo implícitamente alguna forma de acceder al estado (que luego usarían las funciones auxiliares puras), ¿verdad? Podrías igualmente hacer eso explícito.
Al tratar con estado mutable, el orden de las escrituras y lecturas no solo importa para la vista interna (qué partes cambian cuándo) sino también para la externa (cuándo cambia el estado completo del objeto). Alguna convención, como "las propiedades (y los getters) son puros, los métodos pueden ser impuros", ayudará mucho.