Home > OS >  How to support and paint multiple algorithms
How to support and paint multiple algorithms

Time:12-04

I have been working on a maze generator/solver program. It works nicely, but I came across a problem while refactoring my code.

Basically, I used to call methods that draw the process of solving a maze directly from another class, as opposed to calling the paintComponent method (or rather the repaint method). As obviously that is not a good practice, nor is it satisfactory performance-wise I am trying to get around that.

The problem is, to draw the sub-steps of a solution, different solver algorithms need different types and numbers of arguments. I could store these in a class, then call the paintComponent method, in which I invoke the method that paints the sub-steps using the aforementioned arguments.

Unfortunately, that would mean that I have to create a bunch of other classes that extend JPanel, just so I can store the necessary collections and variables, just so I can draw that one specific solution.

Is there a nicer way to get around this, or should I just give up and do it the way I mentioned?

So what I am trying to do:

@Override
public void paintComponent(Graphics g){
    super(g);
    drawMaze(g);
    switch(solverType) //Based on what solver is assigned to the maze it calls the proper method
    {
       case solver1:
          solver1Drawer(g, additional arguments);
       break;
       case solver2:
          solver2Drawer(g, different kind, and number of arguments);
       break;
       //Other cases, with other method calls
    }
}

CodePudding user response:

There are many ways to build such applications that can accept different solvers and views. I'll try to demonstrate a very basic, stripped-down one in an effort to make it as simple as possible.
For this purpose, we'll use a solver that calculates the perimeter of a certain shape and draw the solution.
For a start, we'll define some interfaces, the use of which should become clearer later:

interface Model{
    boolean solve();
    int solution();
    boolean isSolved();
}

interface View{
    void draw(Graphics g);
}

interface Solver {
    Model getModel();
    View getView();
}

Using these interfaces we'll define the Model, the View and the Controller to support the calculation of a perimeter of a square.
The model encapsulates the information (attributes and states) and logic :

class SquarePrimeterModel implements Model{

    private final int edgeLength;
    private int perimeter;
    private boolean isSolved = false;

    public SquarePrimeterModel(int edgeLength) {
        this.edgeLength = edgeLength;
    }

    @Override
    public boolean solve() {
        perimeter = 4 * edgeLength;
        isSolved = true;
        return true;
    }

    @Override
    public int solution() {
        return perimeter;
    }

    @Override
    public boolean isSolved() {
        return isSolved;
    }

    //edgeLength is a unique property for this model
    public int getEdgeLength() {
        return edgeLength;
    }
}

The responsibility of the view, as its name suggests to generate the view:

class SquarePrimeterView implements View{

    private final static int xOffset = 50, yOffset = 50, GAP = 20;
    private final SquarePrimeterModel model;

    public SquarePrimeterView(SquarePrimeterModel model) {
        this.model = model;
    }

    @Override
    public void draw(Graphics g) {
        if(model.isSolved()){
            g.drawRect(xOffset, yOffset, model.getEdgeLength(), model.getEdgeLength());
            String text = "Edge ="   model.getEdgeLength()   " Perimiter ="  model.solution();
            int yPosition = yOffset   model.getEdgeLength()  GAP;
            g.drawString(text, xOffset, yPosition);
        }
    }
}

The controller constructs, configures and manages the model and the view:

class SquarePrimeterController implements Solver{

    private final SquarePrimeterModel model;
    private final View view;

    public SquarePrimeterController(int edgeLength) {
        model = new SquarePrimeterModel(edgeLength);
        view = new SquarePrimeterView(model);
    }

    @Override
    public Model getModel() {
        return model;
    }

    @Override
    public View getView() {
        return view;
    }
}

If we need another solver, for example, a solver to calculate the perimeter of a triangle, we simply have to write TriangelPrimeterController, TriangelPrimeterModel and TriangelPrimeterView, very similar to the SquarePrimeter classes.

The last pice of code that we need is the application that uses SquarePrimeterController:

public class SwingMVCSolveController {

    public SwingMVCSolveController() {
        Solver solver = new SquarePrimeterController(150);//todo: select solver by gui 
        solver.getModel().solve(); //todo start solve by gui 
        new MainView(solver.getView());
    }

    public static void main(String[] args) {
        new SwingMVCSolveController();
    }
}

class MainView extends JPanel {

    private static final Dimension size = new Dimension(400, 400);
    private final View solverView;
    public MainView(View view) {
        solverView = view;
        createAndShowGui();
    }

    private void createAndShowGui() {
        JFrame frame = new JFrame ();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.add (this);
        frame.pack();
        frame.setVisible (true);
    }

    @Override
    public Dimension getPreferredSize() {
        return size;
    }

    @Override
    protected void paintComponent(Graphics g){
        super.paintComponent(g);
        solverView.draw(g);
    }
}

Clearly Solver solver = new SquarePrimeterController(150); can be changed to Solver solver = new TrianglePrimeterController(150); or any other implementation of Solver.

A full runnable code is available here

  • Related