Проектирование неизменяемых и изменяемых объектов в Java - PullRequest
2 голосов
/ 17 апреля 2011

Моя проблема касается дизайна API.

Допустим, я проектирую вектор (математический / физический смысл).Я хотел бы иметь как неизменяемую, так и изменяемую реализацию.

Тогда у меня есть вектор, который выглядит следующим образом:

public interface Vector {
  public float getX(); public float getY();
  public X add(Vector v);
  public X subtract(Vector v);
  public X multiply(Vector v);
  public float length();
}

Интересно, как я могу гарантировать, что оба изменяемыеи неизменная реализация.Мне не очень нравится подход java.util.List (допускающий изменчивость по умолчанию) и UnsupportedOperationException (), который имеет неизменяемая реализация Guava.

Как я могу разработать «идеальный» интерфейс или абстрактный класс Vector с обоимиэти реализации?

Я думал о чем-то вроде этого:

public interface Vector {
  ...
  public Vector add(Vector v);
  ...
}
public final class ImmutableVector implements Vector {
  ...
  public ImmutableVector add(Vector v) {
    return new ImmutableVector(this.x+v.getX(), this.y+v.getY());
  }
  ...
}
public class MutableVector implements Vector {
  ...
  public MutableVector add(Vector v) {
    this.x += v.getX();
    this.y += v.getY();
    return this;
  }
  ...
}

В общем, я хотел бы проверить, есть ли у этого подхода вопиющие недостатки дизайна, что они и чтоя должен сделать это, чтобы исправить это?


Примечания: "вектор" - пример более общего случая использования.Ради моего вопроса я мог бы переписать интерфейс List или что-то еще.Пожалуйста, обратите внимание на более общий случай использования.


Окончательный выбор после ответов ниже, основанный на времени Joda, как кто-то объяснил, но теперь отредактировал:

/** Basic class, allowing read-only access. */
public abstract class ReadableVector {
  public abstract float getX(); public abstract float getY();
  public final float length() {
    return Vectors.length(this);
  }
  // equals(Object), toString(), hashCode(), toImmutableVectors(), mutableCopy()
}
/** ImmutableVector, not modifiable implementation */
public final class ImmutableVector extends ReadableVector implements Serializable {
  // getters
  // guava-like builder methods (copyOf, of, etc.)
}
/** Mutable implementation */
public class Vector extends ReadableVector implements Serializable {
  // fields, getters and setters
  public void add (ReadableVector v) {/* delegate to Vectors */}
  public void subtract(ReadableVector v) {/* delegate to Vectors */}
  public void multiply(ReadableVector v) {/* delegate to Vectors */}
}
/** Tool class containing all the logic */
public final class Vectors {
  public static ImmutableVector add(ReadableVector v1, ReadableVector v2) {...}
  public static void addTo(Vector v1, ReadableVector v2) {...}
  ...
}

Я изменил Вектор синтерфейс к абстрактному классу, потому что в основном вектор не должен быть ничем иным.

Спасибо всем.

Ответы [ 4 ]

7 голосов
/ 17 апреля 2011

Как пользователь вашей библиотеки Vector, я не хотел бы иметь одну реализацию add, которая изменяет текущий объект, и другую реализацию add (того же интерфейса), которая возвращает новую.

Лучше иметь четкий набор методов, которые не изменяют текущий объект, а затем иметь дополнительные методы в изменяемом векторе, которые изменяют текущий объект.

2 голосов
/ 17 апреля 2011

Не думаю, что с вашим дизайном что-то явно не так. Я нахожу это совершенно правильным. Есть несколько вещей, которые я бы учел на вашем месте:

  • Безрассудные пользователи могут писать код для интерфейс вектор мышления их реализации всегда изменчивы.
  • Неизменность обычно означает больше объектов и снижение производительности из-за необходимости помещать все больше и больше объектов в кучу и вынуждает сборщик мусора выполнять больше работы. Если вашему приложению потребуется выполнить много операций «добавления», вам, возможно, придется заплатить цену. Но эй, вот и вся цель иметь изменяемую версию, верно?
  • Кроме того, если вы пишете для многопоточной среды, вам все равно нужно будет синхронизировать доступ к общим переменным типа Vector, если вы не уверены в реализации прежде всего, если вы хотите гарантировать, что реализация может быть переключена без последствий. Это еще раз доказывает, что может быть трудно писать код, не обращая внимания на детали реализации.
  • Хотя я немного поспорил с @Paulo Eberman в другом посте, я верю, что он совершенно прав. Я думаю, что лучше всего иметь два отдельных интерфейса, один для неизменяемых объектов и один для изменяемых (что может расширить этот последний).

Конечно большинство из этих точек спорно, это только мое мнение. * * 1013

0 голосов
/ 17 апреля 2011

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

public class Vector {

    private Double X;
    private Double Y;

    public Vector(Double x, Double y) {
        X = x;
        Y = y;
    }

    public Double getX() {
        return X;
    }

    public Double getY() {
        return Y;
    }
}

, а затем создал бы класс для выполнения базовых векторных операций:

public class BaseVectorAlgebra {

    public static Vector add(Vector arg1, Vector arg2) {
        return new Vector(arg1.getX() + arg2.getX(), arg1.getY() + arg2.getY());
    }

}

Таким образом, вы получитепростой способ расширить систему, не затрагивая существующие классы и не вводя изменчивость, которая только усложняет ситуацию.

ОБНОВЛЕНИЕ:

Если вы все еще хотите использовать изменяемые векторы,тогда я бы добавил сеттеры SetX и SetY в класс Vector, но поместил решение об изменчивости в BaseVectorAlgebra следующим образом:

public static Vector addInto(Vector arg1, Vector arg2) {
    arg1.setX(arg1.getX() + arg2.getX());
    arg1.setY(arg1.getY() + arg2.getY());

    return arg1;
}

Но на самом деле я не люблю здесь изменчивость, так как это вносит ненужные сложности

0 голосов
/ 17 апреля 2011

Ваша идея в порядке, но вряд ли она идеальна.

Вы не указали дженерики.

Вы предполагаете, что арифметические операции, такие как сложение и вычитание, определены для типов, которые содержит ваш Вектор, что может быть неверно.(Обобщения могут помочь в этом.)

Я не знаю, насколько полезен неизменный вектор в контексте математики и физики.

У идеального API был бы аналогичный класс Matrix, поскольку вам нужно будет выполнять линейную алгебру для математики и физики.

Я бы посмотрел общую математическую библиотеку Apache для вдохновения.Это наследник JAMA.Я считаю, что рассмотрение успешных проектов и реализаций моими игроками - хороший способ учиться.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...