Метод кэширования приводит к неизменяемым объектам

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

public interface Complex {

    double real();

    double imaginary();

    double absolute();

    double angle();

}

Теперь вопрос в том, как лучше всего реализовать его как неизменяемый класс? Самый простой и понятный подход «Я забочусь о производительности только тогда, когда это проблема» - это сохранить реальный и воображаемый класс. inary части как конечные поля и вычисляют абсолютное значение и угол при каждом вызове этих методов. Благодаря этому класс остается небольшим и простым, но очевидно, что последние два метода каждый раз возвращают один и тот же результат.

public final class NonCachingComplex implements Complex {

    private final double real;
    private final double imaginary;

    public NonCachingComplex(double real, double imaginary) {
        this.real = real;
        this.imaginary = imaginary;
    }

    @Override public double real() {
        return real;
    }

    @Override public double imaginary() {
        return imaginary;
    }

    @Override public double absolute() {
        return Math.sqrt((real * real) + (imaginary * imaginary));
    }

    @Override public double angle() {
        return absolute() == 0 ? 0 : (Math.acos(real / absolute()) * Math.signum(imaginary));
    }
}

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

public final class EagerCachingComplex implements Complex {

    private final double real;
    private final double imaginary;

    private final double absolute;
    private final double angle;

    public EagerCachingComplex(double real, double imaginary) {
        this.real = real;
        this.imaginary = imaginary;
        this.absolute = Math.sqrt((real * real) + (imaginary * imaginary));
        this.angle = absolute == 0 ? 0 : (Math.acos(real / absolute()) * Math.signum(imaginary));
    }

    // real() and imaginary() stay the same...

    @Override public double absolute() {
        return absolute;
    }

    @Override public double angle() {
        return angle;
    }
}

Третья возможность, которую я придумал, - это ленивое вычисление абсолютного значения и угла, когда они потребуются впервые. Но, как видите, это делает код немного загроможденным и подверженным ошибкам. Кроме того, я не уверен, что использование модификатора volatile действительно правильно в этом контексте.

public final class LazyCachingComplex implements Complex {

    private final double real;
    private final double imaginary;

    private volatile Double absolute;
    private volatile Double angle;

    public LazyCachingComplex(double real, double imaginary) {
        this.real = real;
        this.imaginary = imaginary;
    }

    // real() and imaginary() stay the same...

    @Override public double absolute() {
        if (absolute == null) {
            absolute = Math.sqrt((real * real) + (imaginary * imaginary));
        }
        return absolute;
    }

    @Override public double angle() {
        if (angle == null) {
            angle = absolute() == 0 ? 0 : (Math.acos(real / absolute()) * Math.signum(imaginary));
        }
        return angle;
    }

}

Итак, мой вопрос: какой из этих трех подходов лучший? Есть ли еще лучший подход? Должен ли я вообще заботиться о производительности и придерживаться первого подхода и думать об оптимизации только тогда, когда производительность становится реальной проблемой?

7
задан Natix 22 February 2012 в 19:11
поделиться