Para crear una aplicación que sea fácil de probar y mantener, necesitas conocer patrones de diseño. MVVM es una de las mejores opciones.
MVVM (Model-View-ViewModel) es un patrón de diseño que te permite dividir una aplicación en tres partes funcionales:
- (Model) Modelo – La lógica principal del programa (interacción con información, cálculos, consultas, etc.)
- (View) Vista – La interfaz de usuario
- (ViewModel) VistaModelo – El modelo de representación actúa como una capa entre la Vista y el Modelo
Esta división te permite acelerar el desarrollo y la mantenibilidad de programas – puedes cambiar un componente sin afectar el código del otro.
Sin embargo, MVVM puede ser difícil de aprender porque es notablemente diferente de los más comunes MVC y el desarrollo de aplicaciones orientadas a eventos.
Cómo Funciona MVVM
Este patrón puede dividirse en un ejemplo del mundo real. Los protagonistas son: una celebridad, un gerente de relaciones públicas y la prensa.
Celebridad (Modelo) | Gerente de Relaciones Públicas (VistaModelo) | Prensa (Vista) |
---|---|---|
Se ocupa de su entorno inmediato de trabajo, sin la distracción de la promoción. Si es necesario, informa a su jefe, que había ocurrido algo que es necesario informar a la prensa. | Recibe la información de las celebridades y la transmite a la prensa. También se puede transmitir a su empleador una solicitud de alguna de periódico en la realización de la entrevista o la oferta de cooperación. | Escribe una publicación sobre la base de los datos recibidos de PR gestor de las celebridades. |
Todos los componentes funcionan juntos, pero sus detalles internos no están relacionados entre sí. Por ejemplo, la prensa puede cambiar su redacción, modificar el diseño del periódico, contratar nuevos autores o pasar a otro propietario. Sin embargo, esto no afectará de ninguna manera las acciones del gerente de PR — él seguirá trabajando como lo hacía antes.
Este patrón permite al programador cambiar partes individuales de la aplicación sin afectar a las demás. También puede dedicarse a un solo componente sin tener una idea de cómo funcionan los otros. Aunque para una comprensión completa de su trabajo, necesita entender todos los aspectos de la escritura de aplicaciones.
Práctica: Escribiendo una Aplicación MVVM
Se puede usar MVVM para el desarrollo en iOS y Android, pero está mejor implementado en WPF. Esto no es sorprendente, ya que el patrón fue inventado por Microsoft para el desarrollo de aplicaciones con interfaz gráfica en Windows.
Podemos entender el concepto de este patrón con el ejemplo de una aplicación de lista de tareas. Para esto, crea un proyecto WPF y añade la siguiente clase (que servirá como modelo):
public class Customer : INotifyPropertyChanged // Se conecta el interfaz que permite notificar sobre el cambio de estado
{
// Datos sobre la tarea
private string name;
private string task;
private int price;
private DateTime deadline;
private bool isSolved;
public Customer(string name, string task, int price, DateTime deadline) // Constructor simple
{
this.name = name;
this.task = task;
this.price = price;
this.deadline = deadline;
this.isSolved = false;
}
// Getters y Setters
public string Name
{
get
{
return this.name;
}
}
public string Task
{
get
{
return this.task;
}
}
public int Price
{
get
{
return this.price;
}
}
public string DeadlineString
{
get
{
return $"{this.deadline.Day}.{this.deadline.Month}.{this.deadline.Year}";
}
}
public bool IsSolved
{
get
{
return this.isSolved;
}
set
{
this.isSolved = value;
OnPropertyChanged("IsSolved"); // Si la propiedad cambia, se llama a un método que notifica el cambio del modelo
OnPropertyChanged("Color"); // Si se han cambiado varios valores, se puede llamar a un método adicional
}
}
public string Color
{
get
{ // Si la tarea está resuelta, se devolverá el color azul, de lo contrario dependerá de si el plazo ha pasado
return this.isSolved ? "Blue" : DateTime.Now.CompareTo(this.deadline) == -1 ? "Black" : "Red";
}
}
public event PropertyChangedEventHandler PropertyChanged; // Evento que se llamará cuando cambie el modelo
public void OnPropertyChanged([CallerMemberName]string prop = "") // Método que le dice a la ViewModel que debe pasar nuevos datos a la vista
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
// Ahora crea el modelo de vista:
public class AppViewModel : INotifyPropertyChanged
{
private Customer selectedCustomer;
private ObservableCollection<Customer> customers;
public AppViewModel()
{
customers = new ObservableCollection<Customer>() // Agregar datos para pruebas
{
new Customer("Josh", "Fix printer", 500, new DateTime(2024, 5, 11)),
new Customer("Josh", "Install fax", 350, new DateTime(2024, 6, 15)),
new Customer("Tyler", "Update soft", 100, new DateTime(2024, 6, 17)),
new Customer("Nico", "Install antivirus", 400, new DateTime(2024, 6, 19)),
new Customer("Tyler", "Fix printer", 500, new DateTime(2024, 6, 21)),
new Customer("Nico", "Update soft", 200, new DateTime(2024, 6, 27))
};
}
public Customer SelectedCustomer
{
get
{
return this.selectedCustomer;
}
set
{
this.selectedCustomer = value;
OnPropertyChanged("SelectedCustomer");
}
}
public ObservableCollection<Customer> Customers
{
get
{
return this.customers;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string prop = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
Una instancia de esta clase se utilizará para intercambiar datos y comandos entre el modelo y la vista. A continuación, debes crear una Vista, la interfaz del programa:
<Window.Resources>
<Style x:Key="Texto">
<Setter Property="TextBlock.Margin" Value="5"/>
<Setter Property="TextBlock.FontSize" Value="14"/>
</Style>
<Style BasedOn="{StaticResource Texto}" TargetType="TextBlock"></Style>
<Style x:Key="TextoNegrita" BasedOn="{StaticResource Texto}">
<Setter Property="TextBlock.FontWeight" Value="DemiBold"/>
</Style>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Border Padding="5">
<ListBox ItemsSource="{Binding Customers}" SelectedItem="{Binding SelectedCustomer}" ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" FontSize="16" FontWeight="DemiBold" Background="{x:Null}"/>
<TextBlock Text="{Binding Task}"/>
<TextBlock Text="{Binding DeadlineString}" TextAlignment="Right" Foreground="{Binding Color}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Border>
<Border Grid.Column="1" Margin="5">
<StackPanel DataContext="{Binding SelectedCustomer}">
<TextBlock Text="Cliente" TextAlignment="Center" FontSize="16" Style="{StaticResource TextoNegrita}"/>
<DockPanel>
<TextBlock Text="Nombre: " Style="{StaticResource TextoNegrita}"/>
<TextBlock Text="{Binding Name}"/>
</DockPanel>
<DockPanel>
<TextBlock Text="Tarea: " Style="{StaticResource TextoNegrita}"/>
<TextBlock Text="{Binding Task}"/>
</DockPanel>
<DockPanel>
<TextBlock Text="Fecha límite: " Style="{StaticResource TextoNegrita}"/>
<TextBlock Text="{Binding DeadlineString}"/>
</DockPanel>
<DockPanel>
<TextBlock Text="Resuelto: " Style="{StaticResource TextoNegrita}"/>
<CheckBox VerticalAlignment="Center" IsChecked="{Binding IsSolved}"/>
</DockPanel>
</StackPanel>
</Border>
</Grid>
En este código se puede reemplazar una parte muy importante de cualquier aplicación MVVM: la vinculación de datos. En lugar de especificar manualmente algunos valores en la interfaz, se agregan automáticamente gracias a un argumento como este:
{Binding Task}
Esto le dice al programa que debe obtener (vincular) datos de la propiedad Task
. A su vez, en el atributo ItemsSource
de la lista se especifica lo siguiente:
{Binding Customers}
Es decir, obtiene la colección Customers
y muestra sus elementos según la plantilla DataTemplate
. Y si algún elemento cambia, esto se reflejará inmediatamente en la aplicación, gracias a la llamada al método OnPropertyChanged()
.
Sin embargo, para usar la vinculación, primero hay que decirle a la aplicación de dónde tomar los datos. Para ello, en el archivo MainWindow.xaml.cs
agrega la siguiente línea:
DataContext = new AppViewModel();
Aparte de esto, no es necesario agregar nada más: la aplicación funcionará sin manejadores de eventos.
Por ejemplo, si el usuario hace clic en un CheckBox
, el valor se enviará inmediatamente al modelo. Es decir, en este caso se utiliza una vinculación de datos bidireccional: no solo pasa el valor del modelo a la vista, sino que también notifica al modelo que algo ha cambiado.
Conclusión
MVVM es uno de los mejores patrones porque te permite enfocarte completamente en crear la interfaz o la lógica de la aplicación. Al mismo tiempo, no necesitas registrar manualmente controladores de eventos o el trabajo del controlador, como en MVC.
La principal desventaja de MVVM es que es difícil de aprender: además del enlace, también necesitas utilizar comandos, y algunas acciones pueden ser difíciles de realizar sin crear una clase o método adicional en el archivo MainWindow.xaml.cs.