简体   繁体   中英

What is the best way to use visitor pattern in a hierarchy of generic classes?

I have the hierarchy of matrix classes. I did't want to change my classes, so I decided to use visitor pattern to include such matrix operations like addition, multiplication, etc. My hierarchy looks like that:

public abstract class Matrix<T> {
    public abstract T GetValue(int i, int j);
    public abstract void SetValue(int i, int j, T value);
    public abstract Matrix<T> Accept(MatrixVisitor<T> visitor, Matrix<T> matrix);
}

public class SquareMatrix<T> : Matrix<T> {}
public class DiagonalMatrix<T> : Matrix<T> {}
public class SymmetricMatrix<T> : Matrix<T> {}

All of these classes implement Accept method:

public override Matrix<T> Accept(MatrixVisitor<T> visitor, Matrix<T> matrix)
        {
            return visitor.Operation(this, matrix);
        }

But I was stucked with addition of two elements which have type T. I don't know will type T have overloaded operator '+'. I decided to use delegate Func such as parameter of concrete visitor. And now my concrete visitor class looks like this:

public class SumOfSquareMatricesVisitor<T> : MatrixVisitor<T>
    {
        public SumOfSquareMatricesVisitor(Func<T, T, T> sumOfTwoTypesOperation)
        {
            this.sumOfTwoTypesOperation = sumOfTwoTypesOperation;
        }

        public override Matrix<T> Operation(Matrix<T> A, Matrix<T> B)
        {
           // example
           // into a loop
           // result = sumOfTwoTypesOperation(A[i,j], B[i,j]);
        }
}

I really hate my way, because if I want to add or multiply square matrix and diagonal matrix I have to create an instance of visitor and define operation again. Is there a more elegant way? Thanks.

Typically the operation being performed is determined by the type of the visitor, and differentiation of the operations by the operand type is defined by the overloaded visitor methods. This is called double dispatch . In your case a visitor implementing some generic binary matrix operation would have to look like this:

public class SomeOperationVisitor<T> : BinaryOperationMatrixVisitor<T>
{
  public SomeOperationVisitor(Func<T, T, T> someItemOp)
  {
    this.someItemOp = someItemOp;
  }

  public override Matrix<T> Operation(SquareMatrix<T> a, SquareMatrix<T> b)
  { ... }

  public override Matrix<T> Operation(SquareMatrix<T> a, DiagonalMatrix<T> b)
  { ... }

  // other methods for all combination of element types
}

Using a visitor to implement a binary operation is quite unusual as you have to implement overloaded methods for all combinations of operand types.

But in your particular case, if you're limited to just additions and multiplications, you don't have to do that. Those operations don't depend on the constraints that inherited classes add to the base Matrix class: as long as two matrices have compatible element types and matching dimensions, you don't care whether they are diagonal or whatnot. So you can implement a generic sum visitor for all types of matrices with just one method:

public class MatrixSumVisitor<T> : BinaryOperationMatrixVisitor<T>
{
  public MatrixSumVisitor(Func<T, T, T> addOp)
  {
    this.addOp = addOp;
  }

  public override Matrix<T> Operation(Matrix<T> a, Matrix<T> b)
  {
    // 1. Check that dimensions of a and b match.
    // 2. Add a and b.
  }
}

But if this is the case, you don't need a visitor at all as you now perform a single dispatch - by the visitor type only. In this case you can ditch the Accept method entirely - it wouldn't do anything useful besides simply forwarding the call. Instead do this:

Matrix<int> a, b;
// ...
MatrixBinaryOp op = new MatrixSum(...);
var sum = op.Operation(a, b);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM