Инкапсуляция изменяемого объекта в объект только для чтения

В настоящее время я внедряю итерационные решатели, которые последовательно улучшают оценку решения конкретной проблемы. Поскольку решение представляет собой довольно большой набор данных, уточнение выполняется на месте.

Я реализовал простой шаблон Observer/Observable, чтобы иметь возможность наблюдать за алгоритмом во время итераций. В частности, решатель предоставляет метод

Foo getCurrentSolution()

который возвращает текущую оценку решения. Затем наблюдатель может сделать некоторые вычисления на основе текущей оценки (например, чтобы решить, достаточно ли хорошее решение и можно ли остановить итерации). Foo является изменчивым, но, конечно, если наблюдатель изменяет текущую оценку решения, это может разрушить итерации решателя.

Следовательно, getCurrentSolution() действительно должен вернуть защитную копию. Но это требует времени и памяти на большие проблемы, поэтому я пришел к другой идее, которая заключается в getCurrentSolution() вернуть новый ReadOnlyFoo(bar), где foo является (изменяемой) текущей оценкой решения, частного для решателя. Идея в том, что ReadOnlyFoo имеет почти такой же интерфейс, как Fooтолько методы, которые могут изменять данные, "деактивируются" (они выдают исключение). Все детали некоторых фиктивных классов приведены ниже.

Мой вопрос: является ли этот подход хорошей практикой? Есть ли лучший шаблон?

Спасибо! Sebastien

public abstract class AbstractFoo{
    public abstract double getValue();

    public abstract void setValue(final double x);

    public abstract AbstractFoo add(AbstractFoo bar);

    public void addToSelf(AbstractFoo bar){
        setValue(getValue + bar.getValue());
    }
}

public class  Foo extends AbstractFoo{
    private double value;

    public Foo(final double x){
        value = x;
    }

    public double getValue(){
        return value;
    }

    public void setValue(final double x){
        value = x;
    }

    public AbstractFoo add(AbstractFoo bar){
        return new Foo(value + bar.getValue());
    }
}

public final class FooReadOnly extends AbstractFoo{
    private final Foo foo;

    public FooReadOnly(AbstractFoo foo){
        this.foo = foo;
    }

    public double getValue(){
        return foo.getValue();
    }

    public void setValue(final double x){
        throw new NotImplementedException("read only object");
    }

    public AbstractFoo add(AbstractFoo bar){
        return foo.add(bar);
    }

    public void addToSelf(AbstractFoo bar){
        throw new NotImplementedException("read only object");
    }
}

4 ответа

Решение

Я бы определил интерфейс Solution содержит только методы только для чтения и изменяемый класс MutableSolution содержащий все методы, и сделать getCurrentSolution() метод вернуть Solution пример. Таким образом, вам не нужно создавать защитную копию или оборачивать свое решение в оболочку только для чтения.

Конечно, наблюдатель все еще может бросить решение MutableSolution, но это не было бы несчастным случаем. Если вы хотите защитить себя от бросков, напишите ReadOnlySolution реализация класса оболочки Solution и делегирование завернутым MutableSolution, Это похоже на ваше предложение, за исключением того, что подпись метода проясняет, что объект не является изменяемым.

Это на самом деле подход, который Collections класс делает с unmodifiableList(...) и т.д. Он возвращает оболочку, которая содержит исходный список, но выдает исключения в методах, которые изменяют коллекцию.

Я бы не стал этого делать. Если кто-то использует AbstractFoo даже общего интерфейса (который может существовать в вашей реальной реализации), тогда он не знает заранее, является ли текущий экземпляр изменяемым или нет. Таким образом, пользователь рискует выбросить некоторые непроверенные исключения.

И для неизменного объекта, будучи неизменным, это вовсе не исключение. Другими словами: я бы не использовал execption, чтобы сигнализировать, что кто-то пытался изменить экземпляр FooReadOnly,

По крайней мере, я бы добавил абстрактный метод boolean isModifiable() в абстрактный класс AbstractFoo так что мы можем проверить, если мы можем изменить объект. И в этом случае нам не нужно выбрасывать исключения - реализация модифицирующих методов может просто ничего не делать.

Почему такой сверхинженерный раствор? Почему бы не иметь один класс и логический атрибут readOnly? Затем выполните checkWriteable() для каждого установщика.

Другие вопросы по тегам