Inheritance hierarchy for number system classes

2019-06-06 06:22发布

For symbolic representation of mathematical expressions, I am trying to build a hierarchy of number system classes.

In addition to Integer and Real, I also need classes like Rational and Complex. I want all of these classes to inter-operate seamlessly with each other.

e.g. Adding a Complex number to an Integer would give a Complex number etc.


I made all of them to implement the Number interface. (NOT java.lang.Number)

For being able to add numbers of different types, I tried making hierarchy like following.

Integer extends Rational extends Real extends Complex

  • This makes an Integer to unnecessarily store imaginary part etc. This overhead is undesired.
  • Also allowing access to imaginary part of an Integer seems improper.

Can anyone suggest a better design where overhead is avoided and interoperation is still possible?

3条回答
唯我独甜
2楼-- · 2019-06-06 06:45

I'd rather create an interface that has something like getRealPart() and getImaginaryPart(). Then your integer can simply return 0 for getImaginaryPart(). That since you want Integer to "be" a Complex, but you don't want Integer to contain the internal implementation of Complex.

查看更多
爱情/是我丢掉的垃圾
3楼-- · 2019-06-06 06:52

I don't see a problem here. Real number is a complex number, integer is a real number. Complex number can be expressed as a + bi and an integer is a complex number, such that a is an integer and b = 0. So every integer has b and it is equal to 0.

You may however consider using composition (and interfaces) over inheritance:

interface Complex {

    Real a();

    Real b();

}

interface Real extends Complex {

    @Override
    default Real b() {
        return new Integer(0);
    }

}

class Integer implements Real {

    public Integer(int value) {
        // ...
    }

    @Override
    public Real a() {
        return this;
    }

    // ...

}

The disadvantage of this approach is that Integer class can override b() method, so maybe inheritance would be better, because you can use final keyword on the method:

abstract class Complex {

    abstract Real a();
    abstract Real b();

}

abstract class Real extends Complex {

    @Override
    public final Real b() {
        return new Integer(0);
    }

}

class Integer extends Real {

    public Integer(int value) {
        // ...
    }

    @Override
    public Real a() {
        return this;
    }

    // ...

}

I have tried to model it myself and I came up with this terrible code below. I am not happy about it, because of the following problems:

  • Interface - InterfaceImpl antipattern
  • IntegerNumber has methods such as realPart() or numerator() and denominator()
  • some numbers (complex and rational) use other numbers, while others (real and integer) use Java primitives

Code:

public class Test {

    public static void main(String[] args) {
        ComplexNumber complexOne = new ComplexNumber(new RealNumber(1.25), new RealNumber(3));
        ComplexNumber complexTwo = new ComplexNumber(new RealNumber(7), new RealNumber(18.875));

        System.out.println("adding two complex numbers:");
        System.out.println(complexOne.add(complexTwo));


        RealNumber realOne = new RealNumber(15.125);
        RealNumber realTwo = new RealNumber(7.375);

        System.out.println("adding two real numbers:");
        System.out.println(realOne.add(realTwo));
        System.out.println(realTwo.add(realOne));

        System.out.println("adding complex and real number:");
        System.out.println(complexOne.add(realOne));
        System.out.println(realOne.add(complexOne));


        RationalNumber rationalOne = new RationalNumber(new IntegerNumber(1), new IntegerNumber(2));
        RationalNumber rationalTwo = new RationalNumber(new IntegerNumber(1), new IntegerNumber(3));

        System.out.println("adding two rational numbers:");
        System.out.println(rationalOne.add(rationalTwo));


        IntegerNumber integerOne = new IntegerNumber(6);
        IntegerNumber integerTwo = new IntegerNumber(7);

        System.out.println("adding two integers:");
        System.out.println(integerOne.add(integerTwo));

        System.out.println("adding real number and integer:");
        System.out.println(integerOne.add(realOne));
        System.out.println(realOne.add(integerOne));

        System.out.println("adding complex number and integer:");
        System.out.println(integerOne.add(complexOne));
        System.out.println(complexOne.add(integerOne));
    }

}

// interfaces

interface Complex {

    Real realPart();
    Real imaginaryPart();

    default Complex add(Complex other) {
        return new ComplexNumber(
                this.realPart().add(other.realPart()),
                this.imaginaryPart().add(other.imaginaryPart())
        );
    }

}

interface Real extends Complex {

    double asDouble();

    @Override
    default Real imaginaryPart() {
        return new IntegerNumber(0);
    }

    default Real add(Real other) {
        return new RealNumber(this.asDouble() + other.asDouble());
    }

}

interface Rational extends Real {

    Integer numerator();
    Integer denominator();

    @Override
    default Real realPart() {
        return new RealNumber(1.0d * numerator().asInt() / denominator().asInt());
    }

    @Override
    default double asDouble() {
        return realPart().asDouble();
    }

    default Rational add(Rational other) {
        return new RationalNumber(
                this.numerator().multiply(other.denominator()).add(this.denominator().multiply(other.numerator())),
                this.denominator().multiply(other.denominator())
        );
    }

}

interface Integer extends Rational {

    int asInt();

    @Override
    default Integer numerator() {
        return new IntegerNumber(asInt());
    }

    @Override
    default Integer denominator() {
        return new IntegerNumber(1);
    }

    default Integer add(Integer other) {
        return new IntegerNumber(this.asInt() + other.asInt());
    }

    default Integer multiply(Integer other) {
        return new IntegerNumber(this.asInt() * other.asInt());
    }

}

// implementations

class ComplexNumber implements Complex {

    private final Real realPart;
    private final Real imaginaryPart;

    public ComplexNumber(Real realPart, Real imaginaryPart) {
        this.realPart = realPart;
        this.imaginaryPart = imaginaryPart;
    }

    @Override
    public Real realPart() {
        return realPart;
    }

    @Override
    public Real imaginaryPart() {
        return imaginaryPart;
    }

    @Override
    public String toString() {
        return String.format("%s + %si", realPart, imaginaryPart);
    }

}

class RealNumber implements Real {

    private final double value;

    public RealNumber(double value) {
        this.value = value;
    }

    @Override
    public Real realPart() {
        return this;
    }

    @Override
    public double asDouble() {
        return value;
    }

    @Override
    public String toString() {
        return "" + value;
    }

}

class RationalNumber implements Rational {

    private final Integer numerator;
    private final Integer denominator;

    public RationalNumber(Integer numerator, Integer denominator) {
        this.numerator = numerator;
        this.denominator = denominator;
    }

    @Override
    public Integer numerator() {
        return numerator;
    }

    @Override
    public Integer denominator() {
        return denominator;
    }

    @Override
    public String toString() {
        return String.format("%s/%s", numerator, denominator);
    }

}

class IntegerNumber implements Integer {

    private final int value;

    public IntegerNumber(int value) {
        this.value = value;
    }

    @Override
    public int asInt() {
        return value;
    }

    @Override
    public String toString() {
        return "" + value;
    }

}

I am wondering whether interfaces should be abstract classes with implemented methods being final. In the end, I think it may be better to just go with simple inheritance and ignore the fact that every integer will have a field for imaginary part.

I hope this will give you some ideas.

查看更多
兄弟一词,经得起流年.
4楼-- · 2019-06-06 07:00
public interface Numberr {
    public Numberr plus(Numberr n);
    public Numberr minus(Numberr n);
    public Numberr multiply(Numberr n);
    public Numberr sqrt();
    ...

    public Class<? extends Numberr> getType();
}

/////////////////////////////////////////////

public class Integerr implements Numberr {

    protected BigInteger value;

    @Override
    public Numberr plus(Numberr n) {
       if (n instanceof Integerr) {
           return value.add(n.value);
       } else {
           // in case of more broad argument type, use method of that class
           return n.plus(this); 
       }
    }

    ....
}

///////////////////////////////////////////////

public class Rational implements Numberr {

   protected BigInteger numerator;
   protected BigInteger denominator;

   @Override
   public Numberr plus(Numberr n) {
       if (n instance of Integerr) {
           return new Rational(numerator.multiply(n.value), denominator); 
       } else if (n instanceof Rational) {
           return new Rational(numerator.multiply(n.denominator).add(n.numerator.multiply(denominator)), denominator.multiply(n.denominator));
       } else {
           return n.plus(this);
       }
   }

   ....

}
查看更多
登录 后发表回答