I'm not sure if SO is the right place to post software design questions, so if there is a better place please let me know and I'll move this post there.
TL;DR: I have a class that is going to have a lot of methods. I don't want it to become bloated, so I want to find away to extract these methods using the Visitor pattern. However, I'm not sure if this is the intent of the pattern, or if I am mis-using it.
I'm playing around with a Matrix
class right now. I keep wanting to add new methods to it, but I don't want to wind up with a bloated large class.
Some of these methods might be:
getInverse(): Matrix
getRREF(): Matrix
transpose(): Matrix
getDeterminant(): Matrix
subtract(m: Matrix): Matrix
multiply(m: Matrix): Matrix
- The list can go on and on and on.
So instead, what I've been thinking about doing is using the Visitor Pattern that I just learned about, but I'm worried that I'm using it incorrectly, or over-using it. Basically my plan is to expose a minimum interface for Matrix like this:
type VectorForEachCallback = (val: number, idx1: number) => void;
type VectorMapCallback = (val: number, idx1: number) => number;
type VectorReduceCallback<T> = (
prevVal: T,
currVal: number,
idx1: number,
vec: IVector
) => T;
interface IVector {
size(): number;
getValue(idx1: number): number;
setValue(idx1: number, val: number): number;
forEach(cb: VectorForEachCallback): void;
map(cb: VectorMapCallback): void;
dot(other: IVector): number;
reduce<T = number>(cb: VectorReduceCallback<T>, initialValue: T): T;
toArray(): number[];
}
interface IMatrix {
isSquare(): boolean;
getValue(row1: number, col1: number): number;
getNumRows(): number;
getNumCols(): number;
getRows(): IVector[];
getCols(): IVector[];
getRow(idx1: number): IVector;
getCol(idx1: number): IVector;
forEach(cb: (val: number, row: number, col: number) => void): void;
map(cb: (val: number, row: number, col: number) => number): IMatrix;
accept<T>(visitor: IMatrixVisitor<T>): T
}
Then I would have a MatrixVisitor interface as follows:
interface IMatrixVisitor<T> {
visit(m: IMatrix): T;
}
Some example visitors might be:
class MatrixMinorVisitor implements IMatrixVisitor<IMatrix> {
row: number;
col: number;
constructor(row: number, col: number) {
this.row = row;
this.col = col;
}
visit(m: IMatrix): IMatrix {
const result = ConcreteMatrix.makeZero(m.getNumRows() - 1, m.getNumCols() - 1);
m.forEach((val, oldRow, oldCol) => {
if (oldRow === this.row || oldCol === this.col) return;
else {
const newRow = oldRow < this.row ? oldRow : oldRow - 1;
const newCol = oldCol < this.col ? oldCol : oldCol - 1;
result.setValue(newRow, newCol, val);
}
});
return result;
}
}
class MatrixDeterminantVisitor implements IMatrixVisitor<number> {
visit(m: IMatrix): number {
if (!m.isSquare()) throw new Error("must be square");
if (m.getNumRows() === 1) return m.getValue(1, 1);
else {
return m.getCols().reduce((sum, col, col0) => {
const minorVisitor = new MatrixMinorVisitor(1, col0 + 1);
const minor = minorVisitor.visit(m);
const minorDet = this.visit(minor)
const factor = (col0 + 1) % 2 === 0 ? -col.getValue(1) : col.getValue(1);
return sum + factor * minorDet;
}, 0);
}
}
}
As a final example, I could calculate a matrix determinant as follows:
function main() {
const matrix = new ConcreteMatrix([[1, 2], [3, 4]]);
const det = matrix.accept(new MatrixDeterminantVisitor());
console.log(`The determinant is ${det}!`);
}
Aucun commentaire:
Enregistrer un commentaire