user4512098
user4512098

Reputation:

Array value restriction by class inheritance

I'm writing signal processing library,
and I want to make this kind of hierarchy.

First, I defined abstract class
that define only "this wrapper class has one double[] field"
i.e.

public abstract class DoubleArray{
    private final double[] array;
    DoubleArray(double[] array){
        this.array = array;
    }
}

(I made constructor package private rather than protected because
I want to restrict usage of constructor outside the package)

(System.arraycopy is not used here
because this constructor is used only by me.)

Next, my first restriction is,
"cannot contain exceptional values"
i.e.

//inside the same package
public class SignalWithoutExceptionalValues extends DoubleArray{
    SignalWithoutExceptionalValues(double[] signal){
        super(signal);
        for(double d : signal)
            if(Double.isNaN(d) || Double.isInfinite(d))
                throw new IllegalArgumentException("cannot contain exceptional values");
    }
}

Next restriction is
"signal is in range of -1.0 ~ 1.0"
i.e.

//inside the same package
public final class OneAmplitudeSignal extends SignalWithoutExceptionalValues{
    OneAmplitudeSignal(double[] signal){
        super(signal);
        for(double d : signal)
            if(d > 1.0 || d < -1.0)
                throw new IllegalArgumentException("is not normalized");
    }
}

However, I think,
first of all,
I have to do for-each argument checking,
and then, after that,
assign to field.

but in this example,
I am forced to assign unchecked array to field,
because DoubleArray constructor must initialize array field.

So, my question is,
is there any strategy to do "first checking then assign"
in this kind of hierarchy.
OR this design is not appropriate for this purpose,
and there is another good design ?

Thank you.

EDIT

thank you for first two answers.
but by those solution,
library user can instantiate "illegal" signal,
because "checking mechanism can be overrided" .

However, I perfer inheritance
because OneAmplitudeSignal object can be
assigned to SignalWithoutExceptionalValues variables.
This fact corresponds to OneAmp~ is subset of SignalWithout~.

Also, amplitude can be checked by someSignal instanceof OneAmplitudeSignal .

And "final" can't be used here
because I want to allow library users
to create new "more restricted signal"
or, "new kind of restriction" .

In addition, root class can be non-abstract .
in this case, "no restriction signal" can also be instantiated.

Upvotes: 2

Views: 67

Answers (2)

Spotted
Spotted

Reputation: 4091

Since you are writing a library, it means that your library could be used in some cases you can't predict now. That's why I would avoid using inheritance because with inheritance you are scealing (enforcing) what you think is right in order to validate a signal by this static inheritance chain: OneAmplitudeSignal > SignalWithoutExceptionalValues > DoubleArray.

I would instead foster composition to do the validation. This is one possible implementation (more generic):

public interface Rule {
    DoublePredicate getPredicate();
    String getReason();
}

public final class ExceptionalValue implements Rule {
    @Override
    public DoublePredicate getPredicate() {
        return d -> Double.isNaN(d) || Double.isInfinite(d);
    }

    @Override
    public String getReason() {
        return "cannot contain exceptional values";
    }
}

public final class GreaterThanOneAmplitude implements Rule {
    @Override
    public DoublePredicate getPredicate() {
        return d -> d > 1.0 || d < -1.0;
    }

    @Override
    public String getReason() {
        return "is not normalized";
    }
}

public final class DoubleArray {
    private final double[] array;

    public DoubleArray(double[] signal, Collection<Rule> rules) {
        for (Rule rule : rules) {
            if (Arrays.stream(signal).anyMatch(rule.getPredicate())) {
                throw new IllegalArgumentException(rule.getReason());
            }
        }

        this.array = signal;
    }

    //...
}

On the other hand, if you don't need to (or want to) be generic simply do that:

public final class DoubleArray {
    private final double[] array;

    public DoubleArray(double[] signal) {
        for(double d : signal) {
            if(Double.isNaN(d) || Double.isInfinite(d)) {
                throw new IllegalArgumentException("cannot contain exceptional values");
            }
            if(d > 1.0 || d < -1.0) {
                throw new IllegalArgumentException("is not normalized");
            }
        }

        this.array = signal;
    }

    //...
}

Upvotes: 0

Eran
Eran

Reputation: 393986

You could extract the checking logic to a separate check method which the base constructor can execute prior to the assignment :

public abstract class DoubleArray{
    private final double[] array;
    protected abstract void check (double[] array);
    DoubleArray(double[] array){
        check(array);
        this.array = array;
    }
}

The sub-classes will override check to supply the required validation logic :

public class SignalWithoutExceptionalValues extends DoubleArray{
    SignalWithoutExceptionalValues(double[] signal){
        super(signal);
    }

    @Override
    protected void check (double[] signal) {
        for(double d : signal)
            if(Double.isNaN(d) || Double.isInfinite(d))
                throw new IllegalArgumentException("cannot contain exceptional values");
    }
}

public final class OneAmplitudeSignal extends SignalWithoutExceptionalValues{
    OneAmplitudeSignal(double[] signal){
        super(signal);
    }

    @Override
    protected void check (double[] signal) {
        super.check ();
        for(double d : signal)
            if(d > 1.0 || d < -1.0)
                throw new IllegalArgumentException("is not normalized");
    }
}

Upvotes: 1

Related Questions