jueves, 8 de enero de 2015

Arquitecura de Android SOLID(A) III

El controlador

En este ejemplo el controlador es extremadamente sencillo y puede que carezca de sentido pero pensad que en una aplicación real tendríamos, además de la modificación del modelo y del modelo de vista, lógica de aplicación, coordinación y orquestación de servicios, acceso a persistencia, etc.

El controlador es invocado por la vista y contiene la lógica para modificar el modelo y el modelo de vista en caso de ser necesario.




public class MarcadorController{

  private MarcadorModel modelo;
  private MarcadorModelView modelView;

  public MarcadorController(MarcadorModel modelo, MarcadorModelView modelView){
      this.modelo = modelo;
      this.modelView = modelView;
  }
  
  public void puntoParaJugador1(){
      this.modelo.jugador1Marca();
  }

  public void puntoParaJugador2(){
      this.modelo.jugador2Marca();
  }

La Vista

Para la vista, lo mejor es crear una clase a parte que no se mezcle con la Activity. Para ello, se enlaza esta nueva clase a crear con el xml de la vista.

<com.altF13.mvc.views.MarcadorView     xmlns:android="http://schemas.android.com/apk/res/android"     android:layout_width="fill_parent"     android:layout_height="fill_parent"     android:orientation="vertical">

Y creamos la clase MarcadorView, que observará el modelo y el modelo de vista para actualizarse:

public class MarcadorView extends LinearLayout implements OnChangeListener<MarcadorModel>, OnChangeListener<MarcadorViewModel> {

  private MarcadorController controller;
  private Button puntoJugador1, puntoJugador2;
  private TextView nombreJugador1, nombreJugador2;
  private TextView juegosJugador1, juegosJugador2;
  private TextView puntosJugador1, puntosJugador2;

  public MarcadorView(Context context, AttributeSet attrs) {
super(context, attrs);
}

  public setController(MarcadorController controller){
      this.controller = controller;
  }
      update(model);
      update(viewModel);

  @Override
  protected void onFinishInflate() {
super.onFinishInflate();

puntoJugador1= (Button)findViewById(R.id.j1btn);
puntoJugador2= (Button)findViewById(R.id.j2btn);

      nombreJugador1 = (TextView)findViewById(R.id.j1nombre);
      nombreJugador2 = (TextView)findViewById(R.id.j2nombre);
      juegosJugador1 = (TextView)findViewById(R.id.j1juegos);
      juegosJugador2 = (TextView)findViewById(R.id.j2juegos);
      puntosJugador1 = (TextView)findViewById(R.id.j1puntos);
      puntosJugador1 = (TextView)findViewById(R.id.j1puntos);

puntoJugador1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
controller.jugador1Marca();
}
});
addBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
controller.jugador2Marca();
}
});

  public void puntoParaJugador1(){
      this.modelo.jugador1Marca();
  }

  public void puntoParaJugador2(){
      this.modelo.jugador2Marca();
  }

  private void update(MarcadorModel model){
      nombreJugador1.setText(model.nombreJugador1());
      nombreJugador2.setText(model.nombreJugador2());
      juegosJugador1.setText(model.getJuegosJugador1());
      juegosJugador2.setText(model.getJuegosJugador2());
      puntosJugador1.setText(model.getPuntosJugador1());
      puntosJugador1.setText(model.getPuntosJugador1());  
  }

  private void update(MarcadorModelView modelView){
      nombreJugador1.getBackGround().setColorFilter(modelView.getColorJugador1());
      nombreJugador2.getBackGround().setColorFilter(modelView.getColorJugador2());   
  }

  @Override
  public void onChange(MarcadorModel model){
      update(model)
  }

  @Override
  public void onChange(MarcadorViewModel viewModel){
    update(viewModel)
  }

}

Ahora, en el evento Create de la Activity creamos el grafo de dependencia de clases y configuramos los observadores:

public class MarcadorActivity extends Activity 
{
    
     private MarcadorView view;

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//codigo que sea necesario para este evento del ciclo de vida de la actividad
MarcadorModel model = new MarcadorModel();
                MarcadorViewModel viewModel = new MarcadorViewModel();
                MarcadorController controller = new MarcadorController(model, viewModel);
view = (MarcadorView)View.inflate(this, R.layout.mainView, null);
                view.setController(controller);
                setListeners();                
setContentView(view);
}

       private void setListeners(){
           model.addListeners(view); //la vista observa el modelo
           model.addListeners(viewModel); // el modelo de vista observa el modelo
          viewModel.addListeners(view); // la vista observa el modelo de vista
       }

         @Override
protected void onResume() {
super.onResume();
                //código que sea necesario para este evento del ciclo de vida de la actividad
}
@Override
protected void onPause() {
super.onPause();
//código que sea necesario para este evento del ciclo de vida de la actividad
}
@Override
protected void onDestroy() {
super.onDestroy();
view.destroy();
//código que sea necesario para este evento del ciclo de vida de la actividad
}
}

Y obtenemos un bonito enlace de datos automático. Cada vez que se pulse un botón la vista llamará al controlador; el controlador modifica el modelo; el modelo notifica sus cambios a la vista que actualiza las etiquetas de puntuación; el modelo también notifica al modelo de vista que actualizá sus datos sobre los colores; el modelo de vista notificá a la vista que ha cambiado sus colores y la vista actualizá los colores de fondo de las etiquetas. Easy as pie :-D

Resumen de lo que hemos conseguido:
  • Quitamos morralla de la actividad. Ahora tiene una única responsabilidad (como debe ser todo buen diseño orientado a objetos) que es controlar el ciclo de vida de la aplicación.
  • Mantenemos una vista "tonta" que no toma decisiones. No se pueden hacer test unitarios a las vistas por lo que no hay que meter lógica alguna a ese nivel.
  • Separamos la lógica de entidad de negocio (modelo) de la lógica de vista (Modelo de Vista) con la que representamos ese modelo, insisto en la responsabilidad única de las clases.
  • Conseguimos un bajo acoplamiento entre clases, por lo que podemos realizar test unitarios en cada uno de los componentes, llegando hasta el nivel más alto posible, que es la lógica de vista con la que representamos el modelo.
  • Aumentamos la coherencia de los componentes y facilitamos la legibilidad gracias al principio de responsabilidad única.
  • El bajo acoplamiento también permite un mantenimientos y/o modificación mucho menos dolorosa y con un bajo impacto de modificaciones en cascada.
  • Todo el equipo de desarrollo sabe donde hay que tocar cuando llegue la hora de las modificaciones o corrección de bugs. No más horas de seguir el flujo de ejecución mentalmente mirando por todo el código fuente hasta encontrar donde se hace tal o cual cosa.
Disclaimer: Este modelo arquitectónico es muy mejorable y además quedan cosas en el tintero como la notificación del controlador a la vista en el caso de, por ejemplo, mostrar un dialogo modal de confirmación o un mensaje de error o notificación. Aun así, creo que con este ejemplo se pueden apreciar las bondades de lo aquí expuesto.

No hay comentarios:

Publicar un comentario