2
\$\begingroup\$

I'm a beginner: I have learned concepts and features which I think I need in my project, but this is my first serious program and it is my internship project. I'm totally confused and need help to tell me is my way of thinking about OOP, this project and etc correct or not?

As you know already from the title, this project is about the calculation of very big numbers. My employer didn't tell me how many digits, but I'm going to do this for up to 1M digits.

I'm using a linked-list to store numbers. I have 2 classes and one interface till now: class Numbers which stores numbers, interface Arthimetic_Operations and a class (which I haven't named yet) to do operations on Number instances using Arthimetic_Operations interface (actually this 2nd class is an idea for now).

Numbers can be complex or non-complex. They real part and imaginary part can have fractional parts.

Now let's look at Number.java, which I will explain further below.

public class Number {

    private class Store {

        private Node[] currentNode;
        private String number;
        private Utility util = new Utility();

        public Store() {
        }

        void setFields(String number, Node... parts) {
            int n = parts.length;
            this.currentNode = new Node[n];
            System.arraycopy(parts, 0, currentNode, 0, n);
            this.number = number;
        }

        void start() {
            Number.SetFields set = new Number.SetFields();
            String boundries[] = subStringBoundries().split("_");
            int i = 0;
            int down, up;
            StringBuilder subString;

            set.setMarks(number);
            while (i < currentNode.length) {
                down = Integer.parseInt(boundries[i].split("-")[0]);
                up = Integer.parseInt(boundries[i].split("-")[1]);
                subString = new StringBuilder((number.substring(down, up)));
                set.setLengthAndDecimalPointIndex(subString.toString(), i);
                if (subString.indexOf(".") != -1) {
                    subString.delete(subString.indexOf("."), subString.indexOf(".") + 1);
                }
                store(currentNode[i], subString.toString());
                i++;
            }
        }

        void store(Node currentNode, String number) {
            int nodes = number.length() / Node.NODE_LENGTH;
            if (number.length() > nodes * Node.NODE_LENGTH) {
                nodes++;
            }
            int subStringLength = 0;
            String subString;
            while (nodes > 0) {
                try {
                    subString = number.substring(subStringLength, subStringLength + Node.NODE_LENGTH);
                    currentNode.setNumber(subString);
                    if (nodes > 1) {
                        currentNode.setNextNode(new Node());
                        currentNode.getNextNode().setPreviousNode(currentNode);
                        currentNode = currentNode.getNextNode();
                    }
                    nodes--;
                    subStringLength += Node.NODE_LENGTH;
                } catch (IndexOutOfBoundsException e) {
                    e.getMessage();
                    subString = number.substring(subStringLength, number.length());
                    currentNode.setNumber(subString);
                    nodes--;
                }
            }
        }

        private String subStringBoundries() {
            StringBuilder boundries = new StringBuilder();
            if (util.hasTwoPart(number)) {
                int complexMarkIndex = number.indexOf('i');
                int operationIndexl;
                if (number.indexOf('-', 1) != -1) {
                    operationIndexl = number.indexOf('-', 1);
                } else {
                    operationIndexl = number.indexOf('+', 1);
                }
                if (util.isInNormalForm(number)) {
                    if (util.firstCharIsInteger(number)) {
                        boundries.append("0-").append(operationIndexl);
                    } else {
                        boundries.append("1-").append(operationIndexl);
                    }
                    if (operationIndexl + 1 == complexMarkIndex) {
                        boundries.append("_").append(complexMarkIndex + 1).append("-").append(number.length());
                    } else {
                        boundries.append("_").append(operationIndexl + 1).append("-").append(number.length() - 1);
                    }
                } else {
                    boundries.append(operationIndexl + 1).append("-").append(number.length());
                    if (complexMarkIndex != operationIndexl - 1) {
                        boundries.append("_").append(complexMarkIndex + 1).append("-").append(operationIndexl);
                    } else if (util.firstCharIsInteger(number)) {
                        boundries.append("_0-").append(complexMarkIndex);
                    } else {
                        boundries.append("_1-").append(complexMarkIndex);
                    }
                }
            } else if (util.isComplex(number)) {
                int complexMarkIndex = number.indexOf('i');
                if (complexMarkIndex == 0) {
                    boundries.append("1-").append(number.length());
                } else if (complexMarkIndex == 1) {
                    boundries.append("2-").append(number.length());
                } else if (util.firstCharIsInteger(number)) {
                    boundries.append("0-").append(number.length() - 1);
                } else {
                    boundries.append("1-").append(number.length() - 1);
                }
            } else if (util.firstCharIsInteger(number)) {
                boundries.append("0-").append(number.length());
            } else {
                boundries.append("1-").append(number.length());
            }
            return boundries.toString();
        }

        private class Utility {

            private boolean isComplex(String number) {
                return (number.indexOf('i') != -1);
            }

            private boolean hasTwoPart(String number) {
                return (number.indexOf('-', 1) != -1 || number.indexOf('+', 1) != -1);
            }

            boolean firstCharIsInteger(String number) {
                return (number.charAt(0) != '-' && number.charAt(0) != '+');
            }

            boolean isInNormalForm(String number) {
                int markIndex = number.indexOf('i');
                int opeIndex;

                if (number.indexOf('-', 1) != -1) {
                    opeIndex = number.indexOf('-', 1);
                } else {
                    opeIndex = number.indexOf('+', 1);
                }

                return (markIndex > opeIndex);
            }
        }

    }

    private class SetFields {

        Number.Store x = new Number.Store();
        Store.Utility util = x.new Utility();

        private void setMarks(String number) {
            int temp;
            if (util.hasTwoPart(number)) {
                temp = number.indexOf('-', 1);
                if (temp == -1) {
                    temp = number.indexOf('+', 1);
                }
                if (util.isInNormalForm(number)) {
                    if (number.charAt(0) == '-') {
                        real.mark = mark.NEGATIVE;
                    }
                    if (number.charAt(temp) == '-') {
                        imaginary.mark = mark.NEGATIVE;
                    }
                } else {
                    if (number.charAt(0) == '-') {
                        imaginary.mark = mark.NEGATIVE;
                    }

                    if (number.charAt(temp) == '-') {
                        real.mark = mark.NEGATIVE;
                    }
                }
            } else if (util.isComplex(number)) {
                if (number.charAt(0) == '-') {
                    imaginary.mark = mark.NEGATIVE;
                }
            } else if (number.charAt(0) == '-') {
                real.mark = mark.NEGATIVE;
            }
        }

        private void setLengthAndDecimalPointIndex(String number, int key) {
            int index;
            switch (key) {
                case 0:
                    index = number.indexOf('.');
                    if (index != -1) {
                        real.integerPartLength = number.substring(0, index).length();
                        real.fractionalPartLength = number.substring(index + 1, number.length()).length();
                        if (real.integerPartLength == 1) {
                            if (number.charAt(0) == '0') {
                                real.integerPartLength = 0;
                            }
                        }
                    } else {
                        real.fractionalPartLength = 0;
                        real.integerPartLength = number.length();
                    }

                    break;
                case 1:
                    index = number.indexOf('.');
                    if (index != -1) {
                        imaginary.integerPartLength = number.substring(0, index).length();
                        imaginary.fractionalPartLength = number.substring(index + 1, number.length()).length();
                    } else {
                        imaginary.fractionalPartLength = 0;
                        imaginary.integerPartLength = number.length();
                    }
                    if (imaginary.integerPartLength == 1) {
                        if (imaginary.getFirstNode().getNumber().charAt(0) == '0') {
                            imaginary.integerPartLength = 0;
                        }
                    }
                    break;
            }
        }
    }

    public static class Node {

        public static final int NODE_LENGTH = 3;
        private String number;
        private Node nextNode;
        private Node previousNode;

        Node() {
            this.number = "";
        }

        Node(String number) {
            Node temp = this;
            int nodes = number.length() / Node.NODE_LENGTH;
            if (number.length() > nodes * Node.NODE_LENGTH) {
                nodes++;
            }
            int subStringLength = 0;
            String subString;
            while (nodes > 0) {
                try {
                    subString = number.substring(subStringLength, subStringLength + Node.NODE_LENGTH);
                    temp.setNumber(subString);
                    if (nodes > 1) {
                        temp.setNextNode(new Node());
                        temp.getNextNode().setPreviousNode(temp);
                        temp = temp.getNextNode();
                    }
                    nodes--;
                    subStringLength += Node.NODE_LENGTH;
                } catch (IndexOutOfBoundsException e) {
                    e.getMessage();
                    subString = number.substring(subStringLength, number.length());
                    temp.setNumber(subString);
                    nodes--;
                }
            }
        }

        Node(StringBuilder number) {
            Node temp = this;
            int nodes = number.length() / Node.NODE_LENGTH;
            if (number.length() > nodes * Node.NODE_LENGTH) {
                nodes++;
            }
            int subStringLength = 0;
            String subString;
            while (nodes > 0) {
                try {
                    subString = number.substring(subStringLength, subStringLength + Node.NODE_LENGTH);
                    temp.setNumber(subString);
                    if (nodes > 1) {
                        temp.setNextNode(new Node());
                        temp.getNextNode().setPreviousNode(temp);
                        temp = temp.getNextNode();
                    }
                    nodes--;
                    subStringLength += Node.NODE_LENGTH;
                } catch (IndexOutOfBoundsException e) {
                    e.getMessage();
                    subString = number.substring(subStringLength, number.length());
                    temp.setNumber(subString);
                    nodes--;
                }
            }
        }

        public String getNumber() {
            return number;
        }

        public Node getNextNode() {
            return nextNode;
        }

        public Node getPreviousNode() {
            return previousNode;
        }

        public void setNumber(String number) {
            this.number = number;
        }

        public void setNextNode(Node nextNode) {
            this.nextNode = nextNode;
        }

        public void setPreviousNode(Node previousNode) {
            this.previousNode = previousNode;
        }

        public boolean hasNext() {
            return (this.getNextNode() != null);
        }

        public boolean hasPrevious() {
            return (this.getPreviousNode() != null);
        }

        public Node lastNode() {
            Node temp = this;
            try {
                while (temp.hasNext()) {
                    temp = temp.getNextNode();
                }
                return temp;
            } catch (NullPointerException e) {
                System.out.println("Error Occured: " + e.getMessage());
            }
            return null;
        }

    }

    public enum mark {
        POSITIVE, NEGATIVE;
    }

    public class RealPart {

        private Node firstNode;
        private int fractionalPartLength;
        private int integerPartLength;
        private mark mark = Number.mark.POSITIVE;

        public void setFirstNode(Node firstNode) {
            this.firstNode = firstNode;
        }

        public void setFractionalPartLength(int fractionalPartLength) {
            this.fractionalPartLength = fractionalPartLength;
        }

        public void setIntegerPartLength(int integerPartLength) {
            this.integerPartLength = integerPartLength;
        }

        public Node getFirstNode() {
            return firstNode;
        }

        public int getFractionalPartLength() {
            return fractionalPartLength;
        }

        public int getIntegerPartLength() {
            return integerPartLength;
        }

        public mark getMark() {
            return mark;
        }
    }

    public class ImaginaryPart {

        private Node firstNode;
        private int fractionalPartLength;
        private int integerPartLength;
        private mark mark = Number.mark.POSITIVE;

        public void setFirstNode(Node firstNode) {
            this.firstNode = firstNode;
        }

        public Node getFirstNode() {
            return firstNode;
        }

        public int getFractionalPartLength() {
            return fractionalPartLength;
        }

        public int getIntegerPartLength() {
            return integerPartLength;
        }

        public mark getMark() {
            return mark;
        }
    }

    private RealPart real;
    private ImaginaryPart imaginary;

    public Number(String number) {
        Number.Store store = new Number.Store();
        if (store.util.hasTwoPart(number)) {
            real = new RealPart();
            real.setFirstNode(new Node());
            imaginary = new ImaginaryPart();
            imaginary.setFirstNode(new Node());
            store.setFields(number, real.getFirstNode(), imaginary.getFirstNode());
        } else if (store.util.isComplex(number)) {
            imaginary = new ImaginaryPart();
            imaginary.setFirstNode(new Node());
            store.setFields(number, imaginary.getFirstNode());
        } else {
            real = new RealPart();
            real.setFirstNode(new Node());
            store.setFields(number, real.getFirstNode());
        }
        store.start();
    }

    public Number(BufferedReader number) throws IOException {
        this(number.readLine());
    }

    public Number() {
    }

    public ImaginaryPart getImaginary() {
        return imaginary;
    }

    public RealPart getReal() {
        return real;
    }

    public void setImaginary(ImaginaryPart imaginary) {
        this.imaginary = imaginary;
    }

    public void setReal(RealPart real) {
        this.real = real;
    }

    public void print() throws IOException {
        BufferedWriter write = new BufferedWriter(new OutputStreamWriter(System.out));
        class Print {

            private Number.Node currentNode;
            private int nodes = -1;
            private int integerPartLength;

            Print(Number.Node x) {
                this.currentNode = x;
            }

            Print(Number.Node x, int nodes, int integerPartLength) {
                currentNode = x;
                this.nodes = nodes;
                this.integerPartLength = integerPartLength;
            }

            void printNodesValues() throws IOException {
                while (currentNode != null) {
                    write.write(currentNode.getNumber());
                    currentNode = currentNode.getNextNode();
                }
                write.flush();
            }

            void printNodesValues(int nodes) throws IOException {
                int n = nodes;
                while (n > 0) {
                    write.write(currentNode.getNumber());
                    currentNode = currentNode.getNextNode();
                    n--;
                }
                write.flush();
            }

            void start() throws IOException {
                if (nodes == -1) {
                    printNodesValues();
                } else {
                    int n = 0;
                    if (nodes * Number.Node.NODE_LENGTH < integerPartLength) {
                        n = integerPartLength - (nodes * Number.Node.NODE_LENGTH);
                    }
                    printNodesValues(nodes);
                    if (n != 0) {
                        StringBuilder temp = new StringBuilder(currentNode.getNumber());
                        temp.insert(n, '.');
                        write.write(temp.toString());
                        currentNode = currentNode.getNextNode();
                    } else {
                        write.write('.');
                    }
                    printNodesValues();
                }
            }
        }
        Print print;
        if (this.real != null) {
            if (real.getMark() == mark.NEGATIVE) {
                write.write("-");
            }
            if (this.real.fractionalPartLength != 0) {
                int nodes = this.real.integerPartLength / Number.Node.NODE_LENGTH;
                print = new Print(real.getFirstNode(), nodes, real.getIntegerPartLength());
                print.start();
            } else {
                print = new Print(this.real.getFirstNode());
                print.start();
            }
        }
        if (this.imaginary != null) {
            if (imaginary.getMark() == mark.NEGATIVE) {
                write.write("-");
            } else {
                write.write("+");
            }
            if (this.imaginary.fractionalPartLength != 0) {
                int nodes = this.imaginary.integerPartLength / Number.Node.NODE_LENGTH;
                print = new Print(imaginary.getFirstNode(), nodes, imaginary.getIntegerPartLength());
                print.start();
            } else {
                print = new Print(this.imaginary.getFirstNode());
                print.start();
            }
            write.write("i");
        }
        write.write("\n");
        write.flush();
    }
}

To store numbers I have created an inner class called Store. In Store, I specify input number's type, up and down indexes of integer and fractional part and set other fields like number's mark and etc. Is it a good design or is it good to use an inner class to store numbers?, Do I have to remove the class and let the methods be in Number?.

The other inner class, is class Node which is for linked-list's cells. I declared it static and I decided to let it be an inner class because I think I'm using this class and Number class in one place and it's better to be this way! but I am using Node class's instances in Arithmetic_Operations interface for calculations, so I'm not sure it is a good idea not not! so Should I separate Node class?.

I have 3 more inner classes. Two of them are RealPart and ImaginaryPart classes which I'm somehow sure that they are good as an inner class. The other is SetFields for setting number's mark and different part's length. Also I have a local class called Print in print() method which has required methods to print a number, And an inner class in Store class, Utility, which has some required methods to store numbers, as well as Print class.

In summary,

  • What are my class Number issues?
  • How to design it better?
  • Is it ok to have several methods in a class with long bodies or using inner classes is good enough?
  • Am i doing OOP programming in the correct way or not?

I guess it's more than long till now, so if necessary, I'll send another post for Arthimetic_Operations.

\$\endgroup\$
1
\$\begingroup\$

Verbs vs. nouns

As a general rule, in any language, classes, objects, and variables get noun names like Printer, Storage, and Component. Methods get verb names like print, store, and setFields. Having names that violate this is suggestive of a bad object model. It's possible that you could fix this with better names. However, I'm not convinced that you need separate classes for setting the fields or holding the utility methods.

Why reinvent the wheel?

Apache Commons has a complex numbers class. Using that would get you past almost all of the development. Of course, you may not want to install Apache Commons.

Even if you don't use that, the JDK natively supports a BigDecimal class that holds arbitrary length decimal numbers. You could use that to hold the real and imaginary parts of the number. Then you just need to split the input string into real and imaginary parts for parsing. The BigDecimal class will handle parsing the actual numbers. You just need to shave off the whitespace and i (if present). You may need to glue the minus sign to the imaginary part in input like 1 - 5i. Or handle the minus sign manually.

Arbitrary length containers

As stated earlier, you don't really need to write your own arbitrary length container (Node) in this case. The BigDecimal class will do that for you. In other circumstances, you might not have that option. However, you still don't need to write your own implementation. Java already has a LinkedList class. And truthfully, you could just use an ArrayList in this case. You have no need to add or remove items at arbitrary places, which is the advantage of a LinkedList. An ArrayList will handle sizing for you.

I'm less concerned about the object model in terms of Node being separate or not. I would normally make something like that separate so that it could be reused later. But in this case, I don't see the point of using it at all.

You put one class inside another to hide it. For example, if there was functionality specific to complex numbers in Node such that it would be incorrect to use it for other purposes. But I don't see that here.

Printing in Java

The normal idiom for printing in Java is something along the lines of

System.out.println(complexNumber.toString());

Or use a BufferedWriter or whatever. The point being that is rare to write an output routine that is tied to one particular implementation. Instead, most of the time, things convert to String and then use whatever display method from there.

Your implementation is tightly coupled. If someone comes to you wanting to save the value in a file instead, you would have difficulty doing so. With a toString method, you could simply open a different BufferedWriter. If you do write something tightly coupled to BufferedWriter, consider taking an OutputStream as a parameter. Then the caller can determine where to print.

try-with-resources

As a general rule, if you do use a BufferedWriter, you should use it in a try-with-resources so as to be sure that the resource gets closed even if there is an exception.

\$\endgroup\$
  • \$\begingroup\$ thanks for your answer. about using BigDecimal,at the first, i searched about it and i convinced that it is not efficient for this project. actually i can't use that cause of running time. if i use BigDecimal the program's running time will increase so much. Because for calculations, whether i implement operations or use BigDecimal's methods, it wastes a lot of time for converting BigDecimal to String and inverse. so i have to do it directly from the start to lessen the time as much as possible. \$\endgroup\$ – HMD Oct 28 '16 at 0:47
  • \$\begingroup\$ about printing, you are right and i'm thankful for your suggestions. later i'll have to write something that shows/prints numbers in a JTextField, so this is just for now to let me test my program. and as you mentioned it, i need to implement it in a way that also print numbers in a file as user request for it. \$\endgroup\$ – HMD Oct 28 '16 at 0:52
  • \$\begingroup\$ overall, Is my class design good ? what are its issues (except naming elements) ? \$\endgroup\$ – HMD Oct 28 '16 at 0:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.