One approach is to make your functions static and move them into static utility classes.
So instead of
Matrix m = ...;
Matrix mt = m.transpose();
float det = mt.determinant();
Vector x = ..;
Vector b = ..;
Vector y = m.solve(x,b);
You end up with
Matrix m = ...
Matrix mt = MatrixGeometry.transpose(m);
float det = MatrixMath.determinant(m);
Vector x = ..;
Vector b = ..;
Vector y = LinearAlgebra.solve(m,x,b);
The fun part is working out which functions should stay member functions, and which should be static.
There is a down-side if you're using inheritance in your Matrix type though. If your heirachy looks like this,
static interface Matrix { ... }
class DenseMatrix implements Matrix { ... }
class SparseMatrix implements Matrix { ... }
class BandedMatrix implements Matrix { ... }
Then you will probably end up with implementations containing type based dispatch:
class LinearAlgebra {
public static Vector solve(Matrix m, Vector x, Vector b) {
if(m instanceof DenseMatrix)
return solveDense((DenseMatrix)m, x, b);
if(m instanceof SparseMatrix)
return solveSparse((SparseMatrix)m, x, b);
...
}
private solveSparse(SparseMatrix m, Vector x, Vector b) {
...
}
}
Which many would consider a "code smell". You can get around this by using a double-dispatch / visitor style in your Matrix class, but that is a bit more pain to set up - and in many cases is not really much more readable/maintainable.