Questions d'entretien OOP
Les entretiens de programmation orientée objet (OOP) évaluent votre compréhension des concepts fondamentaux comme l'encapsulation, l'héritage, le polymorphisme et l'abstraction, ainsi que votre capacité à appliquer des patrons de conception et à écrire du code propre et maintenable. Ces questions sont courantes pour les postes de génie logiciel à tous les niveaux, du junior au senior, mais surtout pour les postes seniors où les décisions de conception et d'architecture comptent. Les intervieweurs s'attendent à ce que vous expliquiez clairement les concepts, les relatiez à des exemples concrets et résolviez des problèmes de codage démontrant les principes OOP en action.
Ce que couvrent les entretiens OOP
Principes fondamentaux
Questions sur l'encapsulation, l'héritage, le polymorphisme et l'abstraction avec des exemples et des analogies concrètes.
Patrons de conception
Patrons courants comme Singleton, Factory, Observer et Strategy, leurs implémentations et quand les utiliser.
Conception de classes et relations
Héritage vs composition, classes abstraites vs interfaces, couplage/cohésion, principes SOLID.
Problèmes de codage pratiques
Implémentation de hiérarchies de classes, conception d'un parking ou d'un système de bibliothèque, et refactorisation de code procédural vers OOP.
Exemples de questions d'entretien OOP
- Expliquez les quatre piliers de l'OOP et donnez des exemples concrets.Ce qu'une bonne réponse couvre
- L'encapsulation regroupe données et méthodes et cache l'implémentation.
- L'héritage permet à une classe de dériver d'une autre, réutilisant le code.
- Le polymorphisme permet à des objets de différentes classes de répondre à la même interface.
- L'abstraction simplifie la complexité en exposant seulement les fonctionnalités essentielles.
Voir un exemple de réponse
Les quatre piliers de l'OOP sont l'encapsulation, l'héritage, le polymorphisme et l'abstraction. L'encapsulation protège les données en les rendant privées et en fournissant des getters/setters, comme dans une classe CompteBancaire avec solde privé. L'héritage permet à une classe Voiture de hériter de Vehicule, réutilisant des attributs comme vitesse. Le polymorphisme permet à une méthode dessiner() d'être appelée sur une référence de type Forme, mais d'exécuter la version de Cercle ou Rectangle. L'abstraction utilise des classes abstraites ou interfaces pour définir des contrats, comme une interface Paiement avec une méthode payer(). Un piège courant est de confondre abstraction et encapsulation : l'abstraction concerne la conception, l'encapsulation la protection des données.
- Quelle est la différence entre une classe abstraite et une interface ? Quand utiliseriez-vous chacun ?Ce qu'une bonne réponse couvre
- Une classe abstraite peut avoir des méthodes concrètes et abstraites.
- Une interface ne peut avoir que des méthodes abstraites (avant Java 8).
- Une classe peut implémenter plusieurs interfaces, mais ne peut hériter que d'une classe abstraite.
- Utilisez une classe abstraite pour une hiérarchie avec état commun.
- Utilisez une interface pour un contrat partagé entre classes non liées.
Voir un exemple de réponse
Une classe abstraite peut contenir des champs et des méthodes implémentées, tandis qu'une interface (avant Java 8) ne contenait que des signatures de méthodes. Depuis Java 8, les interfaces peuvent avoir des méthodes par défaut et statiques, mais pas de champs d'instance. Une classe peut hériter d'une seule classe abstraite (héritage simple) mais peut implémenter plusieurs interfaces. Utilisez une classe abstraite lorsque les classes filles partagent un état commun (ex: Animal avec attribut age) et que certaines méthodes sont communes. Utilisez une interface pour définir un comportement transversal (ex: Volant pour Oiseau et Avion). Un piège est d'utiliser l'héritage pour réutiliser du code alors qu'une interface serait plus appropriée pour éviter le couplage.
- Implémentez un patron Singleton en Java (ou dans le langage de votre choix) et discutez de la sécurité des threads.Ce qu'une bonne réponse couvre
- Le singleton garantit une seule instance d'une classe.
- Il faut gérer la concurrence avec synchronized ou enum.
- L'initialisation paresseuse peut causer des problèmes de thread.
- L'approche double vérification réduit la contention.
- La meilleure pratique en Java est d'utiliser un enum.
Voir un exemple de réponse
Un patron Singleton restreint l'instanciation d'une classe à une seule instance. Pour la sécurité des threads, plusieurs approches existent : synchronisation de la méthode getInstance (simple mais coûteuse), double vérification (double-checked locking) avec volatile, ou initialisation statique (Eager initialization). L'approche la plus robuste en Java est d'utiliser un enum qui garantit une seule instance et gère la sérialisation. Un piège est d'utiliser un singleton sans gestion de threads, ce qui peut créer plusieurs instances en environnement multithreadé. Le singleton est parfois considéré comme un anti-patron en raison du couplage global.
Solution de référencejava public enum Singleton { INSTANCE; public void operation() { // implémentation } } // Utilisation: Singleton.INSTANCE.operation(); // Temps: O(1) pour l'accès; Espace: O(1) pour l'instance unique. - Concevez une hiérarchie de classes pour les formes (Cercle, Rectangle) avec calcul de surface, démontrant le polymorphisme.Ce qu'une bonne réponse couvre
- Utilisez une classe abstraite ou une interface pour la forme de base.
- Les classes Cercle et Rectangle héritent de Forme et implémentent surface().
- Le polymorphisme permet de traiter différentes formes via une référence de type Forme.
- Le calcul de surface utilise une méthode abstraite surchargée dans chaque sous-classe.
- Un piège est d'oublier de marquer la méthode surface() comme abstraite ou de mauvaise hiérarchie.
Voir un exemple de réponse
La hiérarchie commence par une classe abstraite Forme avec une méthode abstraite calculerSurface(). Cercle et Rectangle héritent de Forme et implémentent calculerSurface() à leur manière. Cela démontre le polymorphisme : une référence de type Forme peut exécuter la méthode appropriée selon l'objet réel. Par exemple, une liste de Forme peut appeler calculerSurface() sans connaître le type exact. Il faut bien définir les attributs nécessaires (rayon, largeur, hauteur). Un piège est d'utiliser une méthode non abstraite qui retourne 0 par défaut, ce qui peut causer des erreurs. Cette conception respecte le principe ouvert/fermé.
Solution de référencejava abstract class Forme { public abstract double calculerSurface(); } class Cercle extends Forme { private double rayon; public Cercle(double rayon) { this.rayon = rayon; } @Override public double calculerSurface() { return Math.PI * rayon * rayon; } } class Rectangle extends Forme { private double largeur, hauteur; public Rectangle(double largeur, double hauteur) { this.largeur = largeur; this.hauteur = hauteur; } @Override public double calculerSurface() { return largeur * hauteur; } } // Utilisation: List<Forme> formes = Arrays.asList(new Cercle(2), new Rectangle(3,4)); // for (Forme f : formes) System.out.println(f.calculerSurface()); // Temps: O(1) par surface; Espace: O(1) par objet. - Comment l'héritage conduit-il à un couplage fort ? Préférez la composition à l'héritage – expliquez avec un exemple.Ce qu'une bonne réponse couvre
- L'héritage lie la classe fille à la classe mère, créant un couplage fort.
- Des modifications dans la classe mère peuvent impacter toutes les classes filles.
- La composition utilise des références à d'autres classes, offrant plus de flexibilité.
- La composition suit le principe de préférer la composition à l'héritage.
- Exemple : une classe Voiture peut avoir un moteur (composition) plutôt qu'hériter de Moteur.
Voir un exemple de réponse
L'héritage conduit à un couplage fort car la classe fille dépend de l'implémentation interne de la classe mère. Tout changement dans la mère (ajout de méthode, modification de comportement) peut casser les filles. Par exemple, si une classe Mere change sa méthode methode() que Fille surcharge, le comportement peut changer de manière inattendue. La composition (has-a) est préférée car elle permet de changer les composants à l'exécution, réduit les dépendances et respecte l'encapsulation. Par exemple, au lieu de faire hériter Voiture de Moteur, on donne à Voiture une référence à un objet Moteur. Cela permet de changer de moteur facilement. Le piège est de tomber dans l'héritage profond (plus de 2 niveaux) ou de forcer une relation 'est-un' qui n'existe pas.
- Écrivez du code pour démontrer comment vous implémenteriez un patron Strategy pour calculer les frais d'expédition.Ce qu'une bonne réponse couvre
- Le patron Strategy définit une famille d'algorithmes interchangeables.
- Utilisez une interface pour la stratégie (ex: CalculFrais).
- Les stratégies concrètes implémentent l'interface.
- Le contexte utilise une référence à la stratégie et délègue le calcul.
- Permet de changer dynamiquement l'algorithme.
Voir un exemple de réponse
Le patron Strategy permet de sélectionner un algorithme à l'exécution. On définit une interface CalculFrais avec une méthode calculerFrais(double poids). Des classes comme FraisStandard, FraisExpress l'implémentent. La classe Commande contient une référence à CalculFrais et appelle sa méthode. Ainsi, on peut changer la stratégie (ex: passer d'Express à Standard) sans modifier la classe Commande. Cela respecte le principe ouvert/fermé. Le piège est d'avoir trop de stratégies ou de ne pas encapsuler correctement le changement. Ce patron est utile pour les calculs de frais, les taxes, les algorithmes de tri, etc.
Solution de référencejava interface CalculFrais { double calculerFrais(double poids); } class FraisStandard implements CalculFrais { public double calculerFrais(double poids) { return poids * 0.5; } } class FraisExpress implements CalculFrais { public double calculerFrais(double poids) { return poids * 1.5 + 10; } } class Commande { private CalculFrais calculFrais; public Commande(CalculFrais cf) { this.calculFrais = cf; } public double getFrais(double poids) { return calculFrais.calculerFrais(poids); } } // Utilisation: Commande c = new Commande(new FraisExpress()); // System.out.println(c.getFrais(5)); // Temps: O(1); Espace: O(1). - Qu'est-ce que le principe de substitution de Liskov ? Fournissez un exemple de code qui le viole.Ce qu'une bonne réponse couvre
- Le principe de Liskov stipule qu'un objet d'une classe dérivée peut remplacer un objet de la classe de base sans altérer le programme.
- Une violation se produit lorsque la sous-classe modifie le comportement attendu.
- Exemple: une classe Rectangle et Carré où Carré modifie la largeur/hauteur ensemble.
- Cela brise le contrat car le comportement de setLargeur() diffère.
- Ne pas respecter LSP conduit à des bugs subtils.
Voir un exemple de réponse
Le principe de substitution de Liskov (LSP) énonce que si S est un sous-type de T, alors les objets de type T peuvent être remplacés par des objets de type S sans altérer les propriétés du programme. Une violation classique est l'héritage entre Rectangle et Carré. Si Carré hérite de Rectangle et surcharge setLargeur() pour ajuster aussi la hauteur, alors un code qui utilise un Rectangle et fixe seulement la largeur ne fonctionnera pas correctement pour un Carré. Pour respecter LSP, il vaut mieux utiliser une classe commune Forme ou la composition. Autre exemple : une classe Oiseau avec une méthode voler() et une sous-classe Pingouin qui ne vole pas. Un piège est de forcer l'héritage pour réutiliser du code sans respecter le comportement.
Solution de référencejava class Rectangle { private int largeur, hauteur; public void setLargeur(int l) { largeur = l; } public void setHauteur(int h) { hauteur = h; } public int getSurface() { return largeur * hauteur; } } class Carre extends Rectangle { @Override public void setLargeur(int l) { super.setLargeur(l); super.setHauteur(l); } // violation LSP } // Test: Rectangle r = new Carre(); // r.setLargeur(5); r.setHauteur(4); // attend surface 20, mais obtient 16 car le carré force hauteur=5 // Temps: O(1); Espace: O(1) - Concevez un système de commande de restaurant avec des classes pour MenuItem, Order et Payment, en assurant une bonne conception OOP.Ce qu'une bonne réponse couvre
- Menu est une classe de base avec nom et prix.
- Order contient une liste de MenuItems et calcule le total.
- Payment est une classe abstraite avec une méthode payer().
- Utilisez l'héritage pour les types de paiement (Carte, Espèces).
- Assurez la séparation des responsabilités, pas de logique mélangée.
Voir un exemple de réponse
La conception OOP pour un système de commande de restaurant doit respecter les principes SOLID. MenuItem est une classe simple avec attributs nom, prix. Order contient une collection de MenuItems et calcule le total via une méthode. Payment est une classe abstraite avec une méthode abstraite payer(double montant). Les sous-classes Carte, Especes, Cheque implémentent payer(). L'Order peut avoir une référence à Payment pour traiter le paiement. Les responsabilités sont séparées : Order gère les articles et le calcul, Payment gère la transaction. Un piège est de mettre trop de logique dans Order ou de coupler Order à des détails de paiement. Utiliser des interfaces ou classes abstraites pour Payment permet d'ajouter de nouveaux modes de paiement sans modifier Order. Assurez-vous que les données sont encapsulées.
Solution de référencejava class MenuItem { private String nom; private double prix; public MenuItem(String nom, double prix) { this.nom = nom; this.prix = prix; } public double getPrix() { return prix; } } class Order { private List<MenuItem> items = new ArrayList<>(); public void ajouterItem(MenuItem item) { items.add(item); } public double calculerTotal() { return items.stream().mapToDouble(MenuItem::getPrix).sum(); } } abstract class Payment { public abstract void payer(double montant); } class Carte extends Payment { public void payer(double montant) { System.out.println("Payé par carte: " + montant); } } class Especes extends Payment { public void payer(double montant) { System.out.println("Payé en espèces: " + montant); } } // Temps: O(n) pour calculerTotal; Espace: O(n) pour les items.
Comment se préparer
- Maîtrisez les définitions et exemples pour chaque principe OOP ; soyez prêt à expliquer avec des analogies.
- Entraînez-vous à concevoir des systèmes sur un tableau blanc ou en utilisant un éditeur de code, en vous concentrant sur les responsabilités et les relations des classes.
- Révisez les patrons de conception courants et soyez capable de les implémenter à partir de zéro dans votre langage.
- Comprenez les compromis : héritage vs composition, couplage fort vs faible, et quand utiliser abstrait vs interface.
- Préparez-vous au codage pratique : écrivez de petits programmes OOP et soyez à l'aise pour expliquer vos choix de conception.
Questions fréquemment posées
Quelles sont les questions d'entretien OOP les plus courantes ?
Les questions courantes incluent expliquer les quatre piliers, la différence entre classe abstraite et interface, concevoir une hiérarchie de classes et implémenter un patron de conception comme Singleton.
Comment dois-je me préparer pour les problèmes de codage OOP ?
Entraînez-vous à concevoir de petits systèmes comme un parking, une bibliothèque ou un panier e-commerce. Concentrez-vous sur la structure des classes, l'encapsulation et l'utilisation d'interfaces.
Dois-je connaître les patrons de conception pour un entretien OOP ?
Oui, surtout pour les postes seniors. Connaissez les plus courants : Singleton, Factory, Strategy, Observer et Decorator.
Quelle est la différence entre héritage et composition ?
L'héritage crée une relation 'est-un', tandis que la composition crée une relation 'a-un'. Favorisez la composition pour la flexibilité et le couplage faible.
Comment expliquer clairement les concepts OOP ?
Utilisez des analogies concrètes : une voiture comme objet avec des propriétés (couleur, modèle) et des méthodes (démarrer, arrêter), ou une hiérarchie d'animaux de zoo pour expliquer le polymorphisme.
Pratiquez les questions OOP avec des retours instantanés de l'IA
Téléchargez votre CV, obtenez un entretien simulé personnalisé et voyez exactement ce qu'il faut améliorer — gratuit pour commencer.