Методы расширения, которые возвращают тип интерфейса

#generics #interface #extension-methods

#обобщения #интерфейс #методы расширения

Вопрос:

Итак, я писал простой универсальный класс matrix и столкнулся с проблемой, для которой мне не нравится мое решение, поэтому я решил, что попрошу помощи с лучшим.

Рассмотрим интерфейс, описанный здесь:

 public interface IMatrix<T>
{
    void DeleteColumn(int position);
    void DeleteRow(int position);
    // Returns a NEW IMatrix<T>
    IMatrix<T> FromRows(IList<IList<T>> rows);      // would like to remove
    // Returns a NEW IMatrix<T>
    IMatrix<T> FromColumns(IList<IList<T>> columns);// would like to remove
    IList<IList<T>> GetColumns();
    IList<IList<T>> GetRows();
    void InsertColumn(int position);
    void InsertRow(int position);
    void SetValueAt(int row, int column, T value);
}
  

с расширениями

 public static class MatrixExtensions
{
    /// <summary>
    /// Performs a standard matrix addition
    /// </summary>
    public static IMatrix<T> Add<T>(this IMatrix<T> matrix, IMatrix<T> other, IScalarOperators<T> operators)
    {
        JoinCells<T> joiner = new JoinCells<T>();
        return joiner.Join(matrix, other, null, operators.OperatorAdd);
    }

    /// <summary>
    /// Adds a row to the end of the matrix
    /// </summary>
    public static void AddRow<T>(this IMatrix<T> matrix);

    /// <summary>
    /// Adds a number of rows to the end of the matrix
    /// </summary>
    public static void AddRows<T>(this IMatrix<T> matrix, int rows);

    /// <summary>
    /// Adds a column to the end of the matrix
    /// </summary>
    public static void AddColumn<T>(this IMatrix<T> matrix);

    /// <summary>
    /// Adds a number of columns to the end of the matrix
    /// </summary>
    public static void AddColumns<T>(this IMatrix<T> matrix, int columns);

    /// <summary>
    /// Gets the column at the specified position
    /// </summary>
    public static IList<T> ColumnAt<T>(this IMatrix<T> matrix, int position);

    /// <summary>
    /// Gets the number of columns in the matrix
    /// </summary>
    public static int ColumnCount<T>(this IMatrix<T> matrix);

    /// <summary>
    /// Sets the number of columns in the matrix
    /// </summary>
    public static void ColumnCount<T>(this IMatrix<T> matrix, int columns);

    /// <summary>
    /// Deletes the last column from the matrix
    /// </summary>
    public static void DeleteLastColumn<T>(this IMatrix<T> matrix);

    /// <summary>
    /// Deletes the last row from the matrix
    /// </summary>
    public static void DeleteLastRow<T>(this IMatrix<T> matrix);

    /// <summary>
    /// Gets the value at the specified position in the matrix
    /// </summary>
    public static T GetValueAt<T>(this IMatrix<T> matrix, int row, int column);

    /// <summary>
    /// Multiplies this matrix with the other matrix and returns the result
    /// </summary>
    public static IMatrix<T> Multiply<T>(this IMatrix<T> matrix, IMatrix<T> other, IVectorOperators<T> vectorOperators, IScalarOperators<T> scalarOperators)
    {
        JoinRowColumn<T> joiner = new JoinRowColumn<T>();
        return joiner.Join(matrix, other, vectorOperators.OperatorAdd, scalarOperators.OperatorMultiply);
    }

    /// <summary>
    /// Gets the row at the specified position
    /// </summary>
    public static IList<T> RowAt<T>(this IMatrix<T> matrix, int position);

    /// <summary>
    /// Gets the number of rows in the matrix
    /// </summary>
    public static int RowCount<T>(this IMatrix<T> matrix);

    /// <summary>
    /// Sets the number of rows in the matrix
    /// </summary>
    public static void RowCount<T>(this IMatrix<T> matrix, int rows);
}
  

Рассмотрим метод умножения. Результат умножения на объекты IMatrix хорошо известен. Для простоты рассмотрим только целочисленную реализацию матрицы. Чтобы вычислить результат, нам не нужно ничего знать о матрице, кроме того, как работают Multiply(int, int) и Add(int, int). Поскольку оба они известны, мне не нужно ничего другого, чтобы вернуть новую матрицу с этим результатом. Однако я не уверен в наилучшем способе сделать это.

Мой подход состоял в том, чтобы добавить в интерфейс два метода FromRows и FromColumns. Это кажется неправильным, поскольку я не должен форсировать построение матрицы таким конкретным способом (или я так чувствую). Однако это единственный способ, которым я мог выяснить, как вернуть экземпляр этого интерфейса. Я бы построил матрицу в классе joiner, используя IList, и убедился, что коллекция представляет собой определение строки или столбца, а затем использовал метод FromRows. Возможно, это будет иметь больше смысла на примере:

 /// <summary>
/// Class used for joining by combining rows and columns
/// </summary>
/// <typeparam name="T">
/// Type of the values contained in the matrix
/// </typeparam>
class JoinRowColumn<T> : IJoinMatrix<T>
{
    public IMatrix<T> Join(IMatrix<T> a, IMatrix<T> b, IOperateVector<T> vectorOperation, IOperateScalar<T> cellOperation)
    {
        // ensure that the matricies can be joined
        if (a.ColumnCount() != b.RowCount())
        {
            throw new ArgumentException("Cannot join matricies.  Invalid dimensions");
        }

        IList<IList<T>> rowDefinition = IMatrixHelpers.GetRowDefinition<T>(a.RowCount(), b.ColumnCount());
        for (int row = 0; row < a.RowCount(); row  )
        {
            IList<T> aRow = a.RowAt(row);
            for (int col = 0; col < b.ColumnCount(); col  )
            {
                IList<T> bCol = b.ColumnAt(col);
                rowDefinition[row][col] = vectorOperation.Operate(aRow, bCol, cellOperation);
            }
        }
        // I do not like this because it is unclear that the
        // method is returning a NEW instance of IMatrix<T>
        // based on the row definition.  It does not update
        // a to contain the matrix defined by rowDefinition
        return a.FromRows(rowDefinition); // UGLY!
    }
}
  

Итак, в конце метода я использую одну из предоставленных мне матриц для создания новой матрицы (вероятно) того же типа (хотя нет ограничений на то, что возвращает матрица в отношении конкретной реализации). В этом есть часть проблемы; FromRows возвращает НОВЫЙ экземпляр. Однако это не очевидно, и можно подумать, что это обновление матрицы, на которой вызывается метод.

Есть ли лучший шаблон для добавления в способ построения конкретной реализации интерфейса? Или этот метод кажется нормальным?

Я только знакомлюсь с дженериками, поэтому, пожалуйста, потерпите, если я не вижу чего-то очевидного.

Ответ №1:

  • Включите метод с именем Construct(int xDimension, int yDimension) в свой интерфейс, возвращая его новый экземпляр
  • Разработайте реализацию по умолчанию, которую вы используете в таком случае. Поскольку вы кодируете для интерфейсов, никто не должен предполагать конкретную реализацию.

Лично я бы выбрал второй вариант. Вы все равно кодируете для интерфейсов, реализация не должна иметь значения. Вы легко можете вернуть свою реализацию матрицы по умолчанию, и вызывающий объект сможет с ней работать. Кроме того, вам следует подумать о том, чтобы использовать это и для других ваших методов — вместо того, чтобы манипулировать переданной матрицей, создайте новую и управляйте ею.

Это было бы похоже на то, как работает LINQ, и предотвратило бы появление ошибок по пути. Если вы хотите манипулировать текущим объектом, вам не нужны методы расширения.

Комментарии:

1. Я думал о возврате реализации по умолчанию, затем я подумал: «Что, если выбранное мной значение по умолчанию неоптимально …» Я думаю, что это было преждевременно и немного смущает:(

2. Нет, это не так. Я больше не могу сосчитать количество неоптимальных решений на моих руках. Неоптимальные решения не являются проблемой, если вы понимаете, что они есть, и улучшаете их. Мы все учимся во время написания кода, так работает наша профессия.