🧱 Programando con Objetos: Estado

Ahora que hemos establecido una base sólida con clases y objetos, es hora de dar un paso más. En esta segunda lección de “Programando con Objetos”, nos centraremos en un aspecto crucial de cualquier objeto en la programación orientada a objetos: su estado.

Estado

Un objeto puede contender datos. Estos datos serán guardados en propiedades. Una propiedad va a tener un nombre y un tipo (al menos en lenguajes tipados, en los demás lo hara por inferencia o Duck Typing), y puede ser rellenada, es decir, asignarle un valor en cualquier momento luego de la instanciación de la clase. Un lugar común para asignar valores a las propiedades es dentro del constructor para crear un objeto con un estado consistente según el modelado que querramos representar.

  • 📋 Definiendo propiedades y asignando valores
class Payment
{
  private someNumber: number;
  private someString: string;
  
  public construct()
  {
    this.someNumber = 10;
    this.someString = 'Hello, world!';
  }
}

const somePayment = new Payment();

Luego de la instanciación de nuestro objeto Payment, tanto someNumber como someString tendrán como valores 10 y 'Hello, world!' respectivamente.

Los datos contenidos en un objeto son conocidos también como estado del mismo. Si esos datos van a ser fijos o lo que denominados hardcoded, como vimos anteriormente, podríamos hacerlo en la definición de la propiedad como readonly ó definiendo una constante para cada dato (según el lenguaje de programación se hará de diferentes maneras, si es que fuera posible crear una constante, no siempre es posible…).

  • 📋 Definiendo constantes o valores por defecto en propiedades
class Payment
{
  private readonly PI: number = 3.141516;
  private someString: string = 'Hello, world!';
}

La forma de declarar una constante depende en sí de la sintaxis del lenguaje que utilices, de todas maneras para el ejemplo se entiende la manera utilizada (mediante readonly y valor por defecto en el caso de TypeScript).

Por otro lado, si el valor inicial de una propiedad debe ser variable, podemos dejar que el cliente de nuestra clase nos provea de un valor para nuestra propiedad mediante un argumento de constructor. Agregando el parámetro en el constructor, forzamos a nuestros clientes a proveer de un valor a nuestro argumento en el momento de instanciar nuestra clase.

  • 📋 Agregando un argumento a nuestro constructor
class Payment
{
  private someNumber: number;
  
  public construct(initialNumber: number)
  {
    this.someNumber = initialNumber;
  }
}

const invalidPayment = new Payment(); // No andará ya que el constructor pide un parámetro de entrada (initialNumber)
const validPayment = new Payment(20);

Colocando las variables someNumber y someString como propiedades private (como vimos anteriormente) las hace solamente disponibles a las instancias de Payment. Esto es llamado scoping. Los scopes alternativos a las propiedades pueden ser protected y public. Cuando hacemos una propiedad pública, la hacemos accesible para cualquier cliente (tanto para Payment misma, como para cualquier otra) que utilice la clase.

  • 📋 Definiendo y utilizando propiedades públicas
class Payment
{
    public readonly SOME_NUMBER: number = 10;
    public someString: string;
    
    // ...
}

const validPayment = new Payment();

const number = validPayment.SOME_NUMBER;
validPayment.someString = 'Pagado';

Como someNumber fue definido como constante, no podemos modificarlo, pero si accederlo para traer su valor, en el caso de someString, no es constante, pero si es público por lo que nos permite realizarle modificaciones a su valor y ser accedido para obtener su valor también.


💡 Private should be your default scope

En general, un scope privado es preferible y debería ser tu opción por defecto (siempre que no termines agregando un getX, setX a la clase para poder modificarla igual y accederla facilmente sin una razón real). Limitando el acceso a los datos del objeto, nos ayuda a mantener los detalles de implementación dentro del mismo objeto. Esto asegura que los clientes no tienen que lidiar o manipular directamente cualquier dato del objeto, ya que podrían dejar al objeto en un estado inconsistente, lo que nos lleva a que siempre tendrán que hablar con el objeto a través de los métodos públicos explicitamente definidos, los cuales deberían asegurarnos de dejar siempre a nuestro objeto en estados deseables o consistentes.

Property scoping, es class-based, esto significa que si una propiedad esta como privada, cualquier instancia de la misma clase tiene acceso a esta propiedad en cualquier instancia de la misma clase, incluída ella misma.


  • 📋 Accediendo a una propiedad privada de otra instancia
class Payment
{
  private somePrice: number;
  // ...
  
  public getSomePrice(): number
  {
    return this.somePrice; // Payment, por supuesto, tiene acceso a su propia propiedad somePrice.
  }
  
  public getSomePriceFrom(other: Payment): number
  {
    return other.somePrice; // Payment también tiene acceso a la propiedad privada somePrice de otra instancia de Payment ya que es del mismo tipo de clase.
  }
}

comst somePayment = new Payment();
const otherPayment = new Payment();

const priceOfSomePayment = otherPayment.getSomePriceFrom(somePayment); // Esto retornará el valor de la propiedad somePrice de la instancia somePayment.

Cuando el valor de una propiedad de un objeto puede cambiar durante su ciclo de vida, es considerado un mutable object. Si ninguna de las propiedades del objeto pueden ser modificadas luego de la instanciación del mismo, el objeto es considerado un immutable object.

  • 📋 Objetos: Mutable vs Immutable
class Mutable
{
  private someNumber: number;
  
  public construct(initialNumber: number)
  {
    this.someNumber = initialNumber;
  }
  
  public increase(): void
  {
    this.someNumber = this.someNumber + 1;
  }
}
class Immutable
{
  private someNumber: number;
  
  public construct(initialNumber: number)
  {
    this.someNumber = initialNumber;
  }
  
  public increase(): Immutable
  {
    return new Immutable(this.someNumber + 1);
  }
}
const mutableObject = new Mutable(10);
mutableObject.increase();

Llamando al método increase() de un objeto mutable cambiará el estado del mutableObject en este ejemplo, cambiando el valor de la propiedad someNumber, es decir, al principio era 10 y luego de hacer el increase() al llamar a esta propiedad someNumber del objeto devolverá 11.

const inmutableObject = new Immutable(10);
const newInmutableObject = inmutableObject.increase();

Llamando al método increase() en un objeto immutable no cambiará el estado de la propiedad en el objeto inmutableObject en su lugar recibiremos una nueva instancia de un objeto inmutable con el nuevo valor de la propiedad someNumber incrementado, es decir, que el objeto inmutableObject tendrá un valor de 10 en su propiedad someNumber y el objeto newInmutableObject tendrá el valor de 11 en someNumber.

🌟 Conclusión: La Dinámica de los Estados en la Programación Orientada a Objetos

Hemos indagado sobre la complejidad y la belleza de los estados en los objetos de programación. Desde explorar las propiedades básicas hasta sumergirnos en las profundidades de los objetos mutables e inmutables, hemos visto cómo los estados dan vida y funcionalidad a nuestros objetos. Recordad que la forma en que gestionamos y modificamos estos estados puede tener un impacto significativo en la eficiencia, claridad y mantenibilidad de nuestro código.

💭 Reflexiones Finales y Mirando hacia el Futuro

Como desarrolladores, es fundamental que comprendamos no solo cómo codificar, sino también por qué y cuándo elegir ciertos enfoques sobre otros. El manejo de estados es un pilar en este proceso de toma de decisiones, proporcionando la base para conceptos más avanzados en la programación orientada a objetos que exploraremos en futuras lecciones.

Así que, mientras avanzamos en nuestro camino como programadores, recordemos siempre la importancia de construir una base sólida y reflexiva en nuestros conocimientos.

sign
Written on November 13, 2023