Patrones de Diseño - nahumrosillo/Torni-Juegos GitHub Wiki

1. Patrones de Creación:

Singleton

Clase: Memory

Motivación:

Consideramos que tener varias instancias de una base de datos en memoria, no tiene sentido.

Implementación:

// memory.ts

export class Memory implements BD
{
      private static instance: Memory;
      /...

      static get getInstance() 
      {
    	     if (this.instance === null || this.instance === undefined) 
             {
                 this.instance = new Memory();
             }
             return this.instance;
      }

      private constructor() 
      { 
            /... 
      }

      /...
}

Desde fuera de la clase no se puede llamar al constructor, es privado. La única manera es llamando al método static getInstance(), que devuelve su propia instancia y solo la primera vez se instancia.

Factory Method

Clase: Match

Motivación:

Veíamos que Tournament tenía muchas responsabilidades, entre ellas, tenía que crear todas las partidas y una vez que estas se iban finalizando, volvía a crear nuevas partidas, y con el fin de que en el futuro podemos crear mas tipos de partidas, actualmente solo existe la interfaz Match y un tipo concreto de Match, que se llama NormalMatch, pero en el futuro pensamos que podrían existir otros tipos, como por ejemplo: QuickMarch.

Implementación:

export interface MatchFactory {

	createMatchs(tournament: Tournament, teams: Array<Team>): Array<Match>;
}
export class NormalMatchFactory implements MatchFactory {

	private currentDateMatch: Date;
	private difDateMatchs: number;
	private teams: Array<Team>;

	constructor(startT: Date, endT: Date, nTeams: number) {

		this.currentDateMatch = startT;
		this.difDateMatchs = (endT.getTime() - startT.getTime()) / (nTeams-2);

		this.teams = new Array<Team>();
	}
 
	createMatchs(tournament: Tournament, teams: Array<Team>): Array<Match> {


		for(let i: number = 0 ; i < teams.length ; ++i)
			this.teams.push(teams[i]);

		let matchs: Array<Match> = new Array<Match>();

		while(this.teams.length >= 2) {

			let teamLocal: Team = this.teams.shift();
			let teamVisitor: Team = this.teams.shift();
			let fecha: Date = new Date(this.currentDateMatch);

			matchs.push(new NormalMatch(tournament, fecha, teamLocal, teamVisitor));

			this.currentDateMatch.setTime(this.currentDateMatch.getTime() + this.difDateMatchs);
		}
		return matchs;
	}
}

Explicación:

Como vemos en la implementación, esta factoría únicamente tiene un método que se llama createMatchs, que devuelve un array de Match concretos, en este caso, como solo existe NormalMatchFactory que crea NormalMatch, a partir de los equipos que recibe y los equipos que no pudiese competir en la ronda anterior si las hubiere, que están incluido dentro de la factoría.

Creación:

  1. El tournament concreto llama al método de creación de la factoría concreta de Match inyectado por parámetro.
  2. Este crea las partidas concretas y lo devuelve en un array.

Clase: Tournament

Motivación:

La clase Game solo podía contener un tipo de torneo, pero pensamos que en el futuro podrían existir mas tipos de torneos, aunque actualmente solo existe NormalTournament, en el futuro se podría crear la clase QuickTournament, por ejemplo.

Implementación:

export interface TournamentFactory {

	createTournament(name: string, startIns: Date, endIns: Date, 
			startTour: Date,  endTour: Date, teams: Array<Team>, typeMatch: MatchFactory): Tournament
}

export class NormalTournamentFactory implements TournamentFactory {

	private newTournament: Tournament;

	NormalTournamentFactory() {}

	createTournament(name: string, startIns: Date, endIns: Date, 
			startTour: Date,  endTour: Date, teams: Array<Team>, typeMatch: MatchFactory): Tournament
	{
		this.newTournament = new NormalTournament(name, startIns, endIns, startTour, endTour, teams, typeMatch);

		return this.newTournament;
	}
}

Explicación:

Como vemos en la implementación, esta factoría únicamente tiene un método que se llama createTournament, que devuelve un tipo concreto de Tournament, en este caso solo existe NormalTournamentFactory, que crea NormalTournament.

Creación:

  1. El patrocinador selecciona el tipo de partida que desea crear (Normal, Quick, futuro tipos de partidas).
  2. El patrocinador selecciona el tipo de torneo que desea crear (Normal, Friendly, Quick, etc).
  3. El sistema crear la factoría concreta de Match.
  4. El sistema crea la factoría concreta de Tournament.
  5. El sistema llama al método de creación de la factoría concreta de Tournament recibiendo como parámetro la factoría concreta de Match.
  6. La factoría concreta de Tournament devuelve el Tournament concreto.

2. Patrones Estructurales

No hemos implementado ningún patrón de este tipo, ya que dado nuestro problema, no hemos visto en ningún caso la posibilidad de aplicar ninguno de los patrones tras una investigación de todos estos patrones.

¿Porqué?

  1. Adapter: Pudiera tener sentido si en un futuro alguna interfaz no concuerda con la que se necesita. En esta primera version del proyecto está tan bién diseñado, que no es necesario de crear adaptadores para seguir ampliando el sistema.

  2. Bridge: La creacion de algún tipo de partida o torneo se hace de manera concreta. Se crea y se guarda en la base de datos. En ningun momento necesitamos cambiar de implementacion de un tipo de partida o torneo concreto.

  3. Composite: En nuestro software, no existe un Componente que de manera recursiva pueda ser otro componente:

3.1. Game tendrá solo Tournaments.

3.2. Tournament tendrá solo Matchs.

3.3. Match tendrá solo equipos (en concreto para NormalMatch únicamente 2).

3.4. Team tendrá solo Jugadores.

  1. Decorator: No necesitamos añadir responsabilidades adicionales en tiempo de ejecucion. Las responsabilidades estan asignadas a cada clase concreta. Si hay que definir nuevas responsabilidades se tira de nuevas interfaces.

  2. Facade: La arquitectura de Angular favorece la creación de componentes muy concretos sin la necesidad de crear fachadas que interactuen con subsistemas complejos. En nuestro sistema, desarrollamos los componentes en horizontal, y no en vertical que genere dependencias.

  3. Flyweight: Este patron no es aplicable a nuestro proyecto. No necesitamos ahorro en almacenamiento.

  4. Proxy: Este patron no es aplicable a nuestro proyecto. Nuestro proyecto no es tan complejo para necesitar que algunos objetos sean proxy. O tienes el objeto completo o no lo tienes.

3. Patrones de Comportamiento

Iterator

Clases: Team, Tournament, Ranking y Game

Motivación:

Dado que en todos los casos hemos utilizado un Array como clase contenedora, y este aunque posee Iteradores, su utilización es muy variada a los iteradores de otros lenguajes. Principalmente porque TypeScript no tiene Iteradores, sino que o tiras de JavaScript o usas alguna biblioteca externa.

TypeScript ofrece una alternativa a Javascript pero hay que utilizar varias clases para que todo funcione, dado esta dificultad y mala implementación, hemos decidido aportarle nosotros un iterador implementado por nosotros mismos a estas clases. Queda mas elegante, simple y oculta detalles de la implementación.

Implementación:

export interface Iterator
{
    begin(): any;
    prev(): any;
    next(): any;
    end(): any;
    hasNext(): boolean;
    hasPrev(): boolean;
    current(): any;
}

export interface Aggregator {
    iterator(): Iterator;
}

export class IndexIterator implements Iterator
{
    private index: number = 0;
    private collection: any;

    constructor(collection: any) {
        this.collection = collection;
    }

    begin(): any {
        return this.collection[0];
    }
    
    next(): any {
       return this.collection[this.index++]; 
    }

    prev(): any {
        return this.collection[this.index--];
    }

    hasNext(): boolean {
        return this.index < this.collection.length;
    } 

    hasPrev(): boolean {
        return this.index !== 0;
    }

    end(): any {
        return this.collection[this.collection.length-1];
    }

    current(): any {
      return this.collection[this.index];
    }
}

Explicación:

Tenemos la interfaz Iterator, que es la que estará visible desde el lado del cliente. También tenemos la interfaz Aggregator que de esta extenderá nuestras clases y luego tenemos un tipo de implementación de Iterator, lo llamamos IndexIterator, ya que el Array se puede acceder a través de índices, pero esta clase no la ve el cliente.

Nuestras clases únicamente tienen un método llamado iterator() obtenido de la interfaz Aggregator que internamente creará el tipo concreto de Iterator y devolverá un Iterator (la interfaz común de todos los iterators concretos.

Si iteramos un Team, podremos ver todos los usuarios pertenecientes a dicho equipo. Si iteramos un Tournament, podremos ver todas las partidas que existen en el torneo actualmente. Si iteramos un Ranking, podremos ver todos los ScoreTeam, que cada uno de ellos almacena el equipo, la puntuación de cada uno y veces que ha ganado o perdido. Si iteramos un Game, podremos ver todos los Tournament pertenecientes a dicho Game.

Observer

Clase: Tournament, Match y Ranking

Motivación:

Necesitabamos conseguir que Match avisara al torneo una vez que este finalice para este poder actualizarse, si todas las partidas actuales están terminadas, torneo crea nuevas partidas con los ganadores de la anterior. Lo mismo pasa cuando Torneo cambia, este debe de avisar al Ranking para que se actualice y mantenga los resultados en tiempo real.

Implementación:

export interface Observer {
	update(...any);
}

export interface Subject {
	registerObserver(observer: Observer);
	removeObserver(observer: Observer);
	notify();
}

Tenemos dos interfaces, el Observer es la clase que se debe de actualizar y Subject es la clase que cuando cambie, tiene que notificar al Observer para que se actualice.

Explicación:

La clase Tournament, implementa ambos roles, ya que se tiene que actualizar, y cuando se actualice, debe de llamar a Ranking para que este se actualice.

La clase Match, tiene el rol de Subject, una vez que la partida finaliza, avisa a torneo para que se actualice y si este no tiene mas partidas no finalizadas, creará nuevas partidas partiendo de los ganadores anteriores.

La clase Ranking, tiene el rol de Object, una vez que torneo avisa al Ranking para que se actualice, este deberá mantener los resultados al día.

⚠️ **GitHub.com Fallback** ⚠️