Instancias de la clase
Tengo una clase, que encapsula los métodos que permiten
trabajar con el
storage del navegador
,
donde la implementación del constructor permite crear instancias
para trabajar con el localStorage
o sessionStorage
del navegador según se requiera.
class Storage // class
{
storageObject;
/**
* Set the storage mechanism in use
*
* @param string storageMechanisms: local | session
*/
constructor(storageMechanisms = 'local') {
this.storageObject = (storageMechanisms
&& storageMechanisms.trim() === 'session')
? window.sessionStorage
: window.localStorage;
}
clear() // void
{
this.storageObject.clear();
}
get(key) // mixed
{
return this.storageObject.getItem(key);
}
has(key) // bool
{
return (this.storageObject.getItem(key));
}
set(key, value) // void
{
this.storageObject.setItem(key.trim(), value);
}
unset(key) // void
{
this.storageObject.removeItem(key);
}
}
Podemos crear una instancia para trabajar con el LocalStorage,
sin usar parámetros (mecanismo por defecto):
const localStorage = new Storage();
A través de la instancia 'localStorage'
tenemos acceso
a la propiedad 'storageObject'
que en este caso contiene
un objeto de tipo 'window.localStorage'
y a los métodos
de la clase, por ejemplo, vamos a guardar y recuperar un número imprimiéndolo
en la consola:
const exampleKey = 'this_is_a_key';
localStorage.set(exampleKey, 756);
console.log(localStorage.set(exampleKey)); // 756
// Ahora, podemos borrar TODO el contenido del LocalStorage con:
localStorage.clear();
Y no hay más. Si quisiéramos usar modificadores de visibilidad,
interfaces, etc. tendríamos a usar
TypeScript
.
Herencia
Declaro e instancio una clase para guardar preferencias de usuario
en local que extiende a Storage
para poder usar sus métodos:
class UserPreferences extends Storage // class
{
constructor(storageMechanisms = 'local') // construct
{
super(storageMechanisms);
}
}
const userPreferences = new UserPreferences();
Puedo repetir el código del ejemplo anterior, usando ahora la instancia
de UserPreferences
, y funciona de la misma forma:
// const exampleKey = 'this_is_a_key'; // ya declarada
userPreferences.set(exampleKey, 756);
console.log(userPreferences.set(exampleKey)); // 756
// Ahora, podemos borrar TODO el contenido del LocalStorage con:
userPreferences.clear();
La clase UserPreferences
no aporta nada
a nivel de comportamiento, pero hace el código más legible:
si vemos la llamada userPreferences.clear();
podemos intuir que se están borrando preferencias de usuario.
Pero para que UserPreferences
sea funcional deberíamos, al menos,
sobrescribir el método clear
para que borre sólo los datos
considerados "preferencias de usuario" y
NO todo el contenido del storage en uso.
¿Cómo saber que datos corresponden a las preferncias del usuario?
Definiendo previamente las claves que pueden usarse.
Para esto usaré una clase en la que almacenaré las preferencias
de usuario por defecto, las hago accesibles en UserPreferences
,
añado también un método que las inicializa y la implementación
de clear
.
Así tendré:
class DefaultUserPreferences // class
{
COLOR_SCHEME = 'dark'; // 'dark' | 'light'
ROWS_BY_PAGE = 25
}
class UserPreferences extends Storage // class
{
arrPreferenceKeys;
objDefaultUserPreferences;
constructor(storageMechanisms = 'local') // construct
{
super(storageMechanisms);
this.objDefaultUserPreferences = new DefaultUserPreferences();
this.arrPreferenceKeys
= Object.getOwnPropertyNames(this.objDefaultUserPreferences);
this.init();
}
// overwrite
clear() // void
{
for (const key of this.arrPreferenceKeys) {
this.unset(key);
}
}
init() // void
{
this.arrPreferenceKeys.forEach(key => {
if (!super.has(key)
|| super.get(key) === 'undefined') {
super.set(key, this.objDefaultUserPreferences[key]);
}
});
}
}
const userPreferences = new UserPreferences();
// Para acceder al valor de la llave COLOR_SCHEME:
console.log(new DefaultUserPreferences().COLOR_SCHEME); // 'dark'
console.log(userPreferences.objDefaultUserPreferences['COLOR_SCHEME']); // 'dark'
He guardado en propiedades de la clase UserPreferences
el objeto que contiene los valores por defecto junto con sus claves
y un array con las mismas, lo que requería para implementar
el método clear
TIP: JavaScript permite acceder a las propiedades de una instancia
con Object.getOwnPropertyNames(new ClassName)
.
Mejorando UserPreferences
Voy a almacenar en el storage un par de valores:
userPreferences.set('xxx', 123);
userPreferences.set('COLOR_SCHEME', 123);
Ahora en el storage tendremos, al menos, esos dos pares
clave-valor y se plantean 2 problemas.
Primero, con la instancia userPreferences
tenemos acceso a todas las acciones sobre el contenido del storage,
sin restricciones, salvo al usar el método clear
que ya hemos acotado y segundo, que para la clave almacenada
en la propiedad COLOR_SCHEME
de DefaultUserPreferences
esperábamos que se guarde un string: 'dark' o 'light', pero podemos guardar
cualquier valor.
La falta de restricciones supone una serie de errores potenciales
que puede paliarse sobreescribiendo otros métodos heredados de Storage
.
El caso más grave es aquel en que para una clave de nuestra "feature"
podemos introducir un valor inesperado.
Voy a reescribir el método set
para impedir introducir
en el storage pares clave-valor ajenos a los que debe
gestionar la instanciade UserPreferences
.
Ademas, veremos también como en caso de sobrescribir un método
como se accede al original heredado de la clase padre,
con la palabra super
que ya aparece en el constructor
y en el método init
donde lo he usado explícitamente
para protegerlo en caso de que los métodos de la clase
has
, get
y el propio set
, como es el caso,
sean sobrescritos.
Añado una nueva clase con las claves que uso para
guardar preferencias y los valores admisibles:
class UserPreferencesKeyAndValues // class
{
COLOR_SCHEME = [
'dark',
'light',
];
ROWS_BY_PAGE = [10, 25, 50, 100];
}
En la clase UserPreferences
añado una nueva propiedad
donde guardo una instancia de UserPreferencesKeyAndValues
y la implementación del método set
:
class UserPreferences extends Storage // class
{
arrPreferenceKeys;
objDefaultUserPreferences;
objUserPreferencesKeyAndValues;
constructor(storageMechanisms = 'local') // construct
{
super(storageMechanisms);
this.objDefaultUserPreferences = new DefaultUserPreferences();
this.objUserPreferencesKeyAndValues = new UserPreferencesKeyAndValues();
this.arrPreferenceKeys
= Object.getOwnPropertyNames(this.objDefaultUserPreferences);
this.init();
}
...
// overwrite
set(key, value) // void
{
if (this.arrPreferenceKeys.includes(key)
&& this.objUserPreferencesKeyAndValues[key]
&& this.objUserPreferencesKeyAndValues[key].includes(value)) {
super.set(key, value);
}
}
}
const userPreferences = new UserPreferences();
Si volvemos a intentar guardar en el storage valores erróneos,
podemos ver el resultado de las nuevas restricciones:
userPreferences.set('xxx', 123);
userPreferences.set('COLOR_SCHEME', 123);
console.log(userPreferences.get('xxx')); // undefined
console.log(userPreferences.get(userPreferences.keys.COLOR_SCHEME)); // 'dark'
El código completo se encuentra
aquí.
2-9-2024