record WindowSize(int width, int height)
= Data oriented programming
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); } ... }
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.
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.
Canvas
avec CoolGraphics
dans une classe CoolGraphicsAdapter
.Canvas
avec SimpleGraphics
dans une classe SimpleGraphicsAdapter
.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));
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)
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)); }); }