Инкапсуляция изменяемого объекта в объект только для чтения - PullRequest
3 голосов
/ 11 августа 2011

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

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

Foo getCurrentSolution()

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

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

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

Спасибо!Себастьян

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 ]

3 голосов
/ 11 августа 2011

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

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

1 голос
/ 11 августа 2011

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

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

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

1 голос
/ 11 августа 2011

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

0 голосов
/ 22 августа 2012

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

...