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
}
}
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