Principios GRASP

[ Arquitectura  ]

Hola de nuevo. Después de los principios SOLID, vamos a dar un pasito más y vamos a tratar de explicar los principios GRASP (General Responsibility Assignment Software Patterns).

GRASP describe 7 patrones que són:

  1. Alta cohesión y bajo acoplamiento
  2. Controlador
  3. Creador
  4. Experto en información
  5. Fabricación pura
  6. Indirección
  7. Polimorfismo
  8. Variaciones Protegidas

1 . Alta cohesión y bajo acoplamiento

Alta cohesión: es la información que almacena una clase, debe de ser coherente y debe estar relacionada con la clase. Podemos encontrar 7 tipos de cohesión:

  1. Cohesión coincidente: la clase tiene varios métodos sin relación entre ellos.
  2. Cohesión lógica: la clase tiene varios métodos relacionados, pero en ejecución sólo uno de ellos se utiliza.
  3. Cohesión temporal: la relación que tienen los métodos de la clase es que se ejecutan simultáneamente.
  4. Cohesión de procedimiento: la relación que tienes lo métodos es de la clase es que se ejecutan secuencialmente.
  5. Cohesión de comunicación: la relación que tienes lo métodos es de la clase es que se ejecutan secuencialmente y afectan a la misma entidad de datos.
  6. Cohesión de información: los métodos de una clase se ejecutan de forma no secuencial, los métodos son independientes entre ellos y tratan sobre la misma entidad.
  7. Cohesión funcional: El método de una clase solo realiza una tarea y solo entiende de una sola entidad.

Bajo Acoplamiento: es la idea de tener las clases lo menos ligadas entre sí, de esta forma si tuviéramos que modificar alguna de estas clases, el resto no debería tener ninguna afectación al cambio. Tenemos 3 tipos de acoplamiento:

  1. Acoplamiento de contenido: cuando una clase tiene una referencia directa con otra clase.
  2. Acoplamiento común: cuando dos clases acceden y modifican un mismo objeto global.
  3. Acoplamiento de control: cuando una clase le envía a otra que es lo que debe hacer y como comportarse.

Veamos un ejemplo:

Esta clase esta perfecta.

public class Saludo {
    private string _to; 
    private string _message;

    public Mensaje(string to, string message) {
        _to = to;
        _message = message;
    }
    public void Saludar() {
        // imprime el saludo
    } 
}

Sin embargo, recibimos una nueva feature:

public class Saludo2 {
    private string _to; 
    private string _message;

    public Mensaje(string to, string message) {
        _to = to;
        _message = message;
    }
    public void Saludar() {
        // imprime el saludo
    } 

    public void Despedir() {
        //imprime la despedida
    }
}

No vamos bien, lo suyo sería:

public interface ISaluda {
    void Saludar(string to, string message); 
}
public interface IDespedida {
    void Despedir(string to, string message);
}

public class User {
    private readonly ISaluda _saluda; 
    private readonly IDespedida _despedida;
    public User(ISaluda saluda, IDespedida despedida) {
        _saluda = saluda;
        _despedida = despedida; 
    }
    public void Educado()
    {   // Saluda y despedir:
        _saluda.Saludar("JRRN", "Hola!")
        _despedida.Despedir("JRRN", "Adiós!"); 
    }
}

2. Controlador

Es el patrón que implementa MVC. Es un patrón que realiza la función de intermediario entre una determinada interfaz y la clase que la implementa. Así, es el controlador quien recibe los datos del usuario y quien los envía a las distintas clases según el método que se va usando, teniendo la lógica de negocio controlada y con la capacidad de ser reutilizada.

public class BookView {
}
public class BookPresenter {
    BookView _vista; 
    BookController _controlador;
    public void AddTitle(string title) {
        _controlador.AddTitle(title); }
}
public class BookController {
    public void AddTitle(string title) {
    }
}

Con Controlador:

public class BookView {
}
public class BookPresenter {
    BookView _vista; 
    BookController _controlador;

    public void Title(string title) {
        _controlador.AddTitle(title); 
    }
}
public class BookController {
    private BookRepository _repository;

    public void AddTitle(string title) {
        var book = _repository.GetById(1);  
        book.AddTitle("title");
        _repository.Update(book); 
    }
}
public class BookRepository {
    public Book GetById(int id) { }
    public void Update(Book book) { }
}
public class Book {
    public void AddTitle(string title) { }
}

3. Creador

El patrón creador nos dice quien es responsable de la creación o quien instancia nuevos objetos y/o clases.

La nueva instancia podrá ser creada por una clase si:

  • Contiene o agrega la clase.
  • Almacena la instancia en algún sitio
  • Sabe como crear el objeto (es ‘Experta’)
  • Usa las instancias creadas del objeto
public class CatalogoPresenter  {
    Catalogo _catalogo_;
    public void GetLibros() { } 
}

public class Catalogo {
    List<Libro> _catalogo; 
}

public sealed class Libro {
}

Aplicando creador:

public class CatalogoPresenter
{
    Catalogo _catalogo;
    public void GetLibros() {  }
}
public class Catalogo {
    Book _book;

    public Catalogo() {
        _book = new Book(); 
    }

    public void Eliminar() {
        _book.Eliminar();  
    }

    public void Actualizar(string title) {
        _book.Actualizar(); 
    }
}
public sealed class Book {
    public void Eliminar(){}
    public void Actualizar(string title){} 
}

4. Experto en información

La responsabilidad de crear un objeto o su implementación, debe realizarla la clase que sabe toda la información necesaria para hacerlo, de este modo obtenemos una mayor cohesión la información esta encapsulada disminuyendo el acoplamiento.

public class Catalogo {
    public List<Book> books { get; set; } 
}
    
public class View {
    Catalogo _catalogo;

    public void CalcularTotal() {
        _catalogo.books.Count();
    }
}

Aplicando experto en información:

public class Catalogo {
    public Book[] _books { get; set; }
    
    public void CalcularTotal() {
        _books.Count();
    }
}
public class View {
    private Catalogo _catalogo;

    public void Total() {
        _catalogo.CalcularTotal(); 
    }
}

5. Fabricación Pura

La fabricación pura es la práctica que ayuda con una clase poco cohesiva y no tiene otra clase donde implementar algunos métodos.

Es decir que se crea una clase “inventada” o, pero que al añadirla mejora la estrucutura del sistema.

Sin embargo, abusar de este patrón provoca clases función o algoritmo -> Clases que tienen un solo método.

public class Coche {
    public void Conduce() { }
    public void Aparca() { } 
}

Con fabricación pura:

public class Coche {
    public void Conduce() {  }
}

public class Automovil {
    public Automovil(CocheView view) {
        view.Aparca(new Coche()); 
    }
}

public class CocheView {
    public void Aparca(Coche coche) {
    }
}

6. Indirección

El patrón de indirección nos permite mejorar el bajo acoplamiento entre dos clases.

Para ello, se asigna la responsabilidad a una clase que medie entre las otras clases para proteger a unas de los posibles cambios de otras.

public class Logger {
    public void LogThis() {
        new SysLog().Log("Log this."); 
    }
}
public class SysLog {
    public void Log(string message) { } 
}

Con indirección:

public class Class {
    public void LogThis() {
        new Logger().Log("Log this!"); }
}
public class Logger {
    public void Log(string message) {
    new SysLog().Log(message); }
}

public class SysLog {
    public void Log(string message) {
    }
}

7. Polimorfismo

Polimorfismo es permitir que varias clases se comporten de manera distinta dependiendo del tipo que sean.

public enum ErrorType {
    Info,
    Error 
}

public class Log {
    public void Write(string message, ErrorType errorType) {
        switch (errorType) {
            case errorType.Info: 
                Console.WriteLine("[Info];{0}", message); break;
            case errorType.Error: 
                Console.WriteLine("[Error]:{0}", message); break;
            } 
    }
}

public class Logger {
    Log _log;
    public void WriteLog() {
    _log.Write("Error.", TipoDeLog.Error);
    }
}

Y con polimorfismo:

public interface ILogMessage {
    string Value { get; }
}

public class Error : ILogMessage {
    private readonly string _message;
    
    public Error(string message) {
        _message = message; 
    }
    public string Value {
        get { 
            return string.Format("[Error];{0}", _message);
        } 
    }
}

public class Info : ILogMessage {
    private readonly string _message;
    
    public Info(string message) {
        _message = message; 
    }
    public string Value {
        get { 
            return string.Format("[Info];{0}", _message);
        } 
    }
}

public class Log {
    public void Write(IMensajeDelLog message) {
        Console.WriteLine(message.Valor); }
}

public class Logger {
    Log _log;

    public void Error() {
        _log.Write(new Error("Error"));
    }
    public void Info() {
        _log.Registrar(new Info("Info"));
    } 
}

8. Variaciones protegidas

Variaciones protegidas, es el principio fundamental de protegerse frente a nuevos requisitos.

Así lo que podamos ver en un análisis previo y sea susceptible a modificaciones lo extraeremos a una interfaz para crear varias implementaciones y extender nuestro código todo lo posible sin que nuestro código sufra por la cohesión y el acoplamiento.

De ésta forma, si nos entra un nuevo requisito, este debe repercutir lo mínimo. Este principio introduce el polimorfismo y la indirección.

Tenemos una clase que cambia la calidad de MP3.

public class Mp3 {
    public void Convert(int quality) {
        // Convert
    } 
}
public class QualitySongController {
    public void ConvertirCalidad(Mp3 song) {
        song.Convert(128); 
    }
}

Ahora nos piden que soporte cualquier formato de imagen:

public interface ISong {
    void Convert(int quality);
}
public class Mp3 : ISong {
    public void Convert(int nuevoAlto) {
        // Convert
    }
}
public class Ogg : ISong {
    public void Convert(int nuevoAlto) {
        // Convert
    }
}
public class MostrarImagenController {
    public void ConvertirCalidad(ISong song) {
        song.Convert(128); 
    }
}

Saludos!




@2017/@2024 - JRRN