POO & Design Patterns

Retour TD Paint 1 et 2

Après l'exercice 5

Taille de la fenêtre minimum : solution standard

Taille de la fenêtre minimum : DOP (1/2)

record WindowSize(int width, int height) = Data oriented programming

Taille de la fenêtre minimum : DOP (2/2)

Le gain de voir les objets comme des datas et de simplifier et de clarifier le code.

public class Drawing {

    ...

    public WindowSize size(){
        return shapes.stream()
                      .map(Shape::minWindowSize)
                      .reduce(new WindowSize(0,0),WindowSize::union);
    }

    ...

}

Exercice 7

Problème : Nous avons deux librairies CoolGraphics et SimpleGraphics qui sont fonctionnellement équivalentes et on veut pouvoir utiliser l'une ou l'autre.

Solution : L'idée générale est de créer une troisième librairie que l'on va être capable de coder en utilisant CoolGraphics et SimpleGraphics.

Exercice 7

    public interface Canvas {

    enum CanvasColor {
        BLACK,WHITE,ORANGE;
    }

    @FunctionalInterface
    interface MouseClickCallBack{
        void onClick(int x,int y);
    }

    void clear(CanvasColor c);
    void drawLine(int x1,int y1,int x2,int y2,CanvasColor c);
    void drawEllipse(int x,int y,int width,int height,CanvasColor c);
    void waitForMouseClick(MouseClickCallBack callback);

    default void drawRectangle(int x,int y,int width,int height,CanvasColor c){
        drawLine(x,y,x+width,y,c);
        drawLine(x,y+height,x+width,y+height,c);
        drawLine(x,y,x,y+height,c);
        drawLine(x+width,y,x+width,y+height,c);
    }

}

Pourquoi ne pas prendre l'énumération Color de CoolGraphics ? Pareil pour l'interface fonctionnelle ? Pourquoi drawRectangle est en defaut.

Exercice 7

  • On implément l'interface Canvas avec CoolGraphics dans une classe CoolGraphicsAdapter.
  • On implément l'interface Canvas avec SimpleGraphics dans une classe SimpleGraphicsAdapter.

Exercice 7

L'application Paint ne dépend que de Canvas

    boolean legacy = args2.length==2 && args2[0].equals("-legacy");
    Path path = Path.of(args2.length==2 ? args2[1] :args2[0]);

    var drawing = Drawing.fromFile(path);

    var windowSize = drawing.minWindowSize().union(new WindowSize(500,500));

    // DISPLAY
    Canvas canvas;
    if (legacy) {
        canvas = new SimpleGraphicsAdapter("area", windowSize.width(),windowSize.height());
    } else {
        canvas = new CoolGraphicsAdapter("area",windowSize.width(), windowSize.height());
    }
    
    drawing.paintAll(canvas);
    canvas.waitForMouseClick((x, y) -> drawing.onClick(canvas, x, y));    

Exercice 8

Avec l'interface Canvas, on perd la flexibilité de faire plusieurs dessins avant de ré-afficher.

    public interface Canvas {

    void clear(CanvasColor c);
    void drawLine(int x1,int y1,int x2,int y2,CanvasColor c);
    void drawEllipse(int x,int y,int width,int height,CanvasColor c);
    void waitForMouseClick(MouseClickCallBack callback);
	void render();    

    default void drawRectangle(int x,int y,int width,int height,CanvasColor c){
    	...
    }

}

Les dessins ne s'affichent qu'après le render quelque soit la libraire graphique utilisée (cf. Liskov Substitution Principal)

Exercice 8

Idée: on stocke la liste des actions (=lambdas) à faire et on les fait au moment du render().

public class SimpleGraphicsAdapter implements CanvasOpt {

    private final SimpleGraphics sg;
    private final ArrayList<Consumer<Graphics2D>> drawingActions = new ArrayList<>();

    @Override
    public void drawLine(int x1, int y1, int x2, int y2, CanvasColor c) {
        drawingActions.add(graphics2D -> {
            graphics2D.setColor(toSimpleGraphicsColor(c));
            graphics2D.drawLine(x1,y1,x2,y2);
        });
    }

    @Override
    public void render() {
        var actionsTodo = List.copyOf(drawingActions); // thread-safety
        drawingActions.clear();
        sg.render(graphics2D -> {
            actionsTodo.forEach(consumer -> consumer.accept(graphics2D));
        });

    }