Tp1 : design patterns - design patterns - télécharger pdf

Exercice sur les Design Patterns - Génie Informatique (3ème année)

1. Ajout d'un constructeur pour créer un PathName depuis une chaîne de caractères

Pour découper une chaîne de caractères représentant un chemin en objets PathName, il faut ajouter une méthode statique dans la classe PathName qui utilise lastIndexOf pour identifier le dernier séparateur et créer récursivement les objets.

2. Indépendance du système d'exploitation (OS)

Utilisez System.getProperty("os.name") pour détecter l'OS et adapter le séparateur de chemin. Voici une proposition de code modifiée :


public class PathName {
    private final PathName directory;
    private final String name;

    public PathName(PathName directory, String name) {
        this.directory = directory;
        this.name = name;
    }

    public static PathName fromString(String path) {
        int lastSeparator = -1;
        String osName = System.getProperty("os.name").toLowerCase();

        if (osName.contains("win")) {
            lastSeparator = path.lastIndexOf('\\');
        } else {
            lastSeparator = path.lastIndexOf('/');
        }

        if (lastSeparator == -1) {
            return new PathName(null, path);
        }

        String dirPath = path.substring(0, lastSeparator);
        String localName = path.substring(lastSeparator + 1);
        return new PathName(fromString(dirPath), localName);
    }

    public PathName getDirectory() {
        return directory;
    }

    public String getLocalName() {
        return name;
    }

    public String getPathName() {
        String separator = System.getProperty("os.name").toLowerCase().contains("win") ? "\\" : "/";
        return directory != null ? directory.getPathName() + separator + name : name;
    }
}


3. Gestion des séparateurs pour MacOS

Introduisez la classe FileSystemTokenizer avec les méthodes suivantes :

  • static List<String> tokenize(String path) : Découpe le chemin en une liste de noms de fichiers/dossiers.
  • static String getSeparator() : Retourne le séparateur approprié pour l'OS.

Modifiez la classe PathName pour utiliser FileSystemTokenizer :


public class PathName {
    private final PathName directory;
    private final String name;

    public PathName(PathName directory, String name) {
        this.directory = directory;
        this.name = name;
    }

    public static PathName fromString(String path) {
        List<String> tokens = FileSystemTokenizer.tokenize(path);
        PathName current = null;

        for (int i = tokens.size() - 1; i >= 0; i--) {
            current = new PathName(current, tokens.get(i));
        }

        return current;
    }

    public PathName getDirectory() {
        return directory;
    }

    public String getLocalName() {
        return name;
    }

    public String getPathName() {
        String separator = FileSystemTokenizer.getSeparator();
        return directory != null ? directory.getPathName() + separator + name : name;
    }
}


4. Garantir un seul représentant de l'objet PathName

Utilisez le Singleton Pattern pour garantir qu'une seule instance de PathName existe. Voici une implémentation possible :


public class PathName {
    private static PathName root = null;
    private final PathName directory;
    private final String name;

    private PathName(PathName directory, String name) {
        this.directory = directory;
        this.name = name;
    }

    public static PathName fromString(String path) {
        if (root == null) {
            root = new PathName(null, "/");
        }
        return root;
    }

    public static PathName getRoot() {
        if (root == null) {
            root = new PathName(null, "/");
        }
        return root;
    }

    public PathName getDirectory() {
        return directory;
    }

    public String getLocalName() {
        return name;
    }

    public String getPathName() {
        String separator = FileSystemTokenizer.getSeparator();
        return directory != null ? directory.getPathName() + separator + name : name;
    }
}


Exercice sur les filtres d'images

1. Pourquoi les constructeurs de Images et ImageOps sont privés ?

Les constructeurs sont déclarés private pour empêcher la création d'instances de ces classes. Cela permet de garantir que les méthodes statiques sont les seuls points d'accès pour manipuler les images et les opérations, ce qui est une bonne pratique pour les classes utilitaires.

2. Solution pour appliquer indifféremment n'importe quel filtre

Utilisez le Strategy Pattern pour encapsuler les filtres dans une interface commune. Voici une implémentation :


public interface FilterStrategy {
    BufferedImage apply(BufferedImage src);
}

public class ImageFilter {
    private final FilterStrategy strategy;

    public ImageFilter(FilterStrategy strategy) {
        this.strategy = strategy;
    }

    public BufferedImage applyFilter(BufferedImage src) {
        return strategy.apply(src);
    }
}


3. Méthode createFilter(String name) et design pattern

La méthode createFilter(String name) doit être statique et placée dans une classe dédiée (ex: FilterFactory). Elle utilise le Factory Method Pattern pour créer des filtres en fonction de leur nom.


public class FilterFactory {
    public static FilterStrategy createFilter(String name) {
        switch (name.toLowerCase()) {
            case "blur": return new BlurFilter();
            case "gray": return new GrayFilter();
            case "rotate": return new RotateFilter();
            default: throw new IllegalArgumentException("Filtre inconnu : " + name);
        }
    }
}


4. Optimisation pour éviter les redondances de filtres

Utilisez le Flyweight Pattern pour stocker les filtres en tant qu'instances partagées. Voici une proposition :


public class FilterFactory {
    private static final Map<String, FilterStrategy> filters = new HashMap<>();

    static {
        filters.put("blur", new BlurFilter());
        filters.put("gray", new GrayFilter());
        filters.put("rotate", new RotateFilter());
    }

    public static FilterStrategy getFilter(String name) {
        return filters.computeIfAbsent(name.toLowerCase(), k -> createFilter(k));
    }

    private static FilterStrategy createFilter(String name) {
        switch (name.toLowerCase()) {
            case "blur": return new BlurFilter();
            case "gray": return new GrayFilter();
            case "rotate": return new RotateFilter();
            default: throw new IllegalArgumentException("Filtre inconnu : " + name);
        }
    }
}


5. Éviter les if-else dans createFilter

Utilisez une Map avec une clé et une valeur pour associer le nom du filtre à sa création, comme suit :


public class FilterFactory {
    private static final Map<String, Supplier<FilterStrategy>> filterMap = new HashMap<>();

    static {
        filterMap.put("blur", BlurFilter::new);
        filterMap.put("gray", GrayFilter::new);
        filterMap.put("rotate", RotateFilter::new);
    }

    public static FilterStrategy getFilter(String name) {
        Supplier<FilterStrategy> factory = filterMap.get(name.toLowerCase());
        if (factory != null) {
            return factory.get();
        }
        throw new IllegalArgumentException("Filtre inconnu : " + name);
    }
}


6. Lecture des filtres depuis un fichier


public class FilterReader {
    public static List<FilterStrategy> readFilters(File filterFile) throws IOException {
        List<FilterStrategy> filters = new ArrayList<>();
        try (Scanner scanner = new Scanner(filterFile)) {
            while (scanner.hasNextLine()) {
                String filterName = scanner.nextLine().trim();
                if (!filterName.isEmpty()) {
                    filters.add(FilterFactory.getFilter(filterName));
                }
            }
        }
        return filters;
    }
}


7. Choisir la façon dont les filtres sont implantés

Utilisez le Bridge Pattern pour séparer l'implémentation des filtres de leur interface. Voici une proposition de code :


public interface FilterStrategy {
    BufferedImage apply(BufferedImage src);
}

public abstract class FilterBridge implements FilterStrategy {
    protected FilterStrategy strategy;

    public FilterBridge(FilterStrategy strategy) {
        this.strategy = strategy;
    }

    @Override
    public BufferedImage apply(BufferedImage src) {
        return strategy.apply(src);
    }
}

public class FilterFactory {
    public static FilterStrategy createFilter(String name) {
        switch (name.toLowerCase()) {
            case "blur": return new BlurFilter();
            case "gray": return new GrayFilter();
            case "rotate": return new RotateFilter();
            default: throw new IllegalArgumentException("Filtre inconnu : " + name);
        }
    }
}


8. Classe Convertir


public class Convertir {
    public static void main(String[] args) {
        if (args.length != 2) {
            System.err.println("Usage: java Convertir  ");
            System.exit(1);
        }

        File filterFile = new File(args[0]);
        File imageFile = new File(args[1]);

        try {
            BufferedImage image = Images.load(imageFile);
            List<FilterStrategy> filters = FilterReader.readFilters(filterFile);

            for (FilterStrategy filter : filters) {
                image = filter.apply(image);
            }

            File outputFile = new File(imageFile.getName() + ".png");
            Images.saveAsPNG(outputFile, image);
        } catch (IOException e) {
            System.err.println("Erreur lors de la conversion : " + e.getMessage());
        }
    }
}


FAQ

1. Qu'est-ce que le Singleton Pattern ?

Le Singleton Pattern est un design pattern qui garantit qu'une classe ne peut avoir qu'une seule instance et fournit un point d'accès global à cette instance.

2. Pourquoi utiliser le Strategy Pattern pour les filtres d'images ?

Le Strategy Pattern permet de définir une famille d'algorithmes, de les encapsuler et de les rendre interchangeables. Cela rend le code plus flexible et extensible.

3. Comment le Flyweight Pattern optimise-t-il la gestion des filtres ?

Le Flyweight Pattern partage des instances de filtres entre différentes opérations, réduisant ainsi la consommation de mémoire et évitant les redondances.

Cela peut vous intéresser :

Partagez vos remarques, questions , propositions d'amélioration ou d'autres cours à ajouter dans notre site

Enregistrer un commentaire (0)
Plus récente Plus ancienne