1

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
    }
}
Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
  • 5
    Take a step back for a moment. Consider the responsibility of the `paintComponent` method, what is it's job? It's job is to paint the current state if the component, it's not really meant to perform complex logic like this. Also consider the fact that `paintComponent` can be called at any time for any number of reasons, many of which you don't control and you need to plan `paintComponent` carefully. Instead, your "solvers" should act as the "model", providing information to the component to allow it to determine the best way to represent the data they contain, this is the bases of MVC – MadProgrammer Dec 01 '21 at 19:13

1 Answers1

1

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

c0der
  • 18,467
  • 6
  • 33
  • 65