Programmation orientée objet en TypeScript

Découpage de la logique grâce à la programmation orientée objet en TypeScript

→ Challenge Correction: Bug-out Shelter #7 – Silent Defense

On poursuit les corrigés de l’édition 2025 de la Battle Dev Thales. Dans ce challenge, il fallait identifier les créneaux respectant à la fois des contraintes temporelles et des seuils d’intensité sonore.

Ce challenge qui présentait beaucoup de données à structurer correctement en amont, est une bonne occasion pour mettre en pratique la programmation orientée objet en TypeScript.

Résolution du challenge

On va créer 3 classes :

  • Detection
  • InterventionWindow
  • MissionControl, qui contiendra la mécanique principale du challenge

Première classe : Detection

On va réaliser le parsing des données issues du challenge dans le constructeur de la classe :

class Detection {

  time: number;
  intensity: number;

  constructor(rawData: string) {
    const [time, intensity] = rawData.split(":").map(Number);
    this.time = time;
    this.intensity = intensity;
  }
}

Un peu d’explications :

  • rawData va contenir par exemple « 1760464202:26 ».
  • On découpe la chaine de caractères grâce à la méthode split(…) puis chaque valeur est transformée en nombre grâce à map(Number)
  • Les 2 valeurs sont stockées dans 2 propriétés time et intensity, qui contiendront donc respectivement 1760464202 et 26.

Seconde classe : InterventionWindow

On va à nouveau réaliser le parsing des données dans le constructeur, mais on aura également une méthode permettant de calculer l’intensité totale du créneau :

class InterventionWindow {

  start: number;
  end: number;
  total: number = 0;

  constructor(rawData: string) {
    const [start, end] = rawData.split("_").map(Number);
    this.start = start;
    this.end = end;
  }

  computeIntensity(detections: Detection[]): void {
    this.total = detections
      .filter(d => d.time >= this.start && d.time <= this.end)
      .reduce((sum, d) => sum + d.intensity, 0);
  }
}

Un peu d’explications :

  • Le constructeur est similaire à celui de la classe Detection. Par contre, on initialise dans la classe une troisième propriété, total, à 0.
  • La méthode computeIntensity prend en paramètre un tableau de Detection pour calculer l’intensité totale
  • On utilise un enchainement de fonctions fléchées :
    • Avec filter(…) on ne conserve que les Detection dont le timestamp est compris entre le début et la fin du créneau
    • Avec reduce(…) on réalise la somme des intensités

Troisième classe : MissionControl

Les deux premières classes définissent nos briques de base. Il nous reste maintenant à les faire collaborer dans une structure centrale : MissionControl.

On commence par son constructeur qui va permettre de stocker les données correctement :

class MissionControl {
  interventionWindows: InterventionWindow[];
  detections: Detection[];
  threshold: number;
  latency: number;

  constructor(windowsData: string[], detectionsData: string[], threshold: number, latency: number) {
    this.interventionWindows = windowsData.map(w => new InterventionWindow(w));
    this.detections = detectionsData.map(d => new Detection(d));
    this.threshold = threshold;
    this.latency = latency;
  }
}

Un peu d’explications :

  • interventionWindows et detections vont contenir des tableaux d’instances des classes créées précédemennt
  • Le constructeur se charge de créer toutes les instances, grâce à map(…)
  • On initialise aussi threshold et latency

On crée ensuite 3 méthodes :

computeAllIntensities(): void {
    this.interventionWindows.forEach(w =>
        w.computeIntensity(this.detections)
    );
}

filterValidWindows(): InterventionWindow[] {
    return this.interventionWindows
        .filter(w => w.total <= this.threshold)
        .sort((a, b) => a.start - b.start);
}

selectSequentialWindows(validWindows: InterventionWindow[]): InterventionWindow[] {
    const selected: InterventionWindow[] = [];
    let lastEnd = -Infinity;

    for (const w of validWindows) {
        if (w.start - lastEnd >= this.latency) {
            selected.push(w);
            lastEnd = w.end;
        }
    }

    return selected;
}

Un peu d’explications :

  • La méthode computeAllIntensities(…) permet de lancer le calcul des intensités totales pour chaque créneau en utilisant la propriété detections.
  • La méthode filterValidWindows(…) permet à la fois de ne conserver que les créneaux dont l’intensité totale est inférieure ou égale à la propriété threshold, puis de trier les créneaux selon leur timestamp de départ (start).
  • Enfin, la méthode selectSequentialWindows(…) permet de ne conserver que les créneaux qui remplissent la condition liée à la propriété latency. Pour cela, on parcourt les créneaux, on compare le début du créneau courant avec la fin du dernier créneau sélectionné.

Et une dernière méthode qui permet d’orchestrer la résolution du challenge :

execute(): string {
    // Mécaniques
    this.computeAllIntensities();
    const valid: InterventionWindow[] = this.filterValidWindows();
    const selected: InterventionWindow[] = this.selectSequentialWindows(valid);

    // Construction de la réponse
    const count: number = selected.length;
    const totalIntensity: number = selected.reduce((sum, w) => sum + w.total, 0);
    return `${count}:${totalIntensity}`;
}

Un peu d’explications :

  • On lance le calcul de toutes les intensités
  • On récupère les créneaux valides selon l’intensité
  • On récupère les créneaux valides selon la latence
  • On récupère le nombre de créneaux valides
  • On calcule leur intensité totale
  • On retourne la réponse

Et enfin, l’appel de cette classe dans le programme principal :

const mission = new MissionControl(windows, detections, threshold, latency);
console.log(mission.execute());

Conclusion

Ce challenge est un excellent exemple de la puissance combinée de la programmation orientée objet (POO) et du typage fort en TypeScript.

Grâce à la POO, chaque entité du problème possède sa propre responsabilité et son comportement clairement définis. Le code devient ainsi plus expressif, plus facile à maintenir, et surtout plus proche du langage métier.

Annexe, le code complet de MissionControl :

class MissionControl {
    interventionWindows: InterventionWindow[];
    detections: Detection[];
    threshold: number;
    latency: number;
  
    constructor(windowsData: string[], detectionsData: string[], threshold: number, latency: number) {
      this.interventionWindows = windowsData.map(w => new InterventionWindow(w));
      this.detections = detectionsData.map(d => new Detection(d));
      this.threshold = threshold;
      this.latency = latency;
    }
    
    computeAllIntensities(): void {
      this.interventionWindows.forEach(w =>
          w.computeIntensity(this.detections)
      );
  }
  
  filterValidWindows(): InterventionWindow[] {
      return this.interventionWindows
          .filter(w => w.total <= this.threshold)
          .sort((a, b) => a.start - b.start);
  }
  
  selectSequentialWindows(validWindows: InterventionWindow[]): InterventionWindow[] {
      const selected: InterventionWindow[] = [];
      let lastEnd = -Infinity;
  
      for (const w of validWindows) {
          if (w.start - lastEnd >= this.latency) {
              selected.push(w);
              lastEnd = w.end;
          }
      }
  
      return selected;
  }
  
  execute(): string {
      // Mécaniques
      this.computeAllIntensities();
      const valid: InterventionWindow[] = this.filterValidWindows();
      const selected: InterventionWindow[] = this.selectSequentialWindows(valid);
  
      // Construction de la réponse
      const count: number = selected.length;
      const totalIntensity: number = selected.reduce((sum, w) => sum + w.total, 0);
      return `${count}:${totalIntensity}`;
  }
}


Qui a codé ce superbe contenu ?

Keep learning

Other content to discover