-1
\$\begingroup\$

Below is implementation 1 of data abstraction for rational number using functional paradigm written in Python:

#Use
def mulRational(x, y):
    """Violate abstraction by using other than constructor and selectors"""
    return Rational(getNumer(x)*getNumer(y), getDenom(x)*getDenom(y)) #x and y are abstract data

def addRational(x, y):
    """Violate abstraction by using other than constructor and selectors"""
    nx, dx = getNumer(x), getDenom(x)
    ny, dy = getNumer(y), getDenom(y)
    return Rational(nx * dy + ny * dx, dx * dy)

def eqRational(x, y):
    """Violate abstraction by using other than constructor and selectors"""
    return getNumer(x) * getDenom(y) == getNumer(y) * getDenom(x)

def toString(x):
    """Violate abstraction by using other than constructor and selectors"""
    return '{0}/{1}'.format(getNumer(x), getDenom(x))

#Representation
# Representation is provided by constructors and selectors using tuples

#Constructor
from fractions import gcd
def Rational(n, d):
    """Construct a rational number x that represents n/d."""
    g = gcd(n, d)
    return (n // g, d // g) #this is concrete representation of a rational number


#Selector
from operator import getitem
def getNumer(x):
    """Return the numerator of rational number x."""
    return getitem(x, 0)

#Selector
def getDenom(x):
    """Return the denominator of rational number x."""
    return getitem(x, 1)

Below is implementation 2 of data abstraction for rational number using functional paradigm written in Python:

#Use
def mul_rational(x, y):
    """Violate abstraction by using other than constructor and selectors"""
    return pair(getitem_pair(x,0) * getitem_pair(y,0), getitem_pair(x,1) * getitem_pair(y,1))

def add_rational(x, y):
    """Violate abstraction by using other than constructor and selectors"""
    nx, dx = getitem_pair(x,0), getitem_pair(x,1)
    ny, dy = getitem_pair(y,0), getitem_pair(y,1)
    return pair(nx * dy + ny * dx, dx * dy)

def eq_rational(x, y):
    """Violate abstraction by using other than constructor and selectors"""
    return getitem_pair(x,0) * getitem_pair(y,1) == getitem_pair(y,0) * getitem_pair(x,1)

def rational_to_string(x):
    """Violate abstraction by using other than constructor and selectors"""
    return '{0}/{1}'.format(getitem_pair(x,0), getitem_pair(x,1))


#Representation
#Representation is provided by constructors and selectors using only higher order functions

def pair(x, y):
    from fractions import gcd
    g = gcd(x, y)
    """Return a functional pair"""
    def dispatch(m):  
        if m == 0:
            return x // g

        elif m == 1:
            return y // g
    return dispatch

def getitem_pair(p, i):
    """Return the element at index iof pair p"""
    return p(i)

In implementation 3 below, class Rationalprovides data abstraction for rational number using OOPS paradigm written in Java:

public class Rational{

    /* Representation starts*/
    /* field */
    private int[] tuple;


    /*Constructor*/
    public Rational(int n, int d){
        this.tuple = new int[2];
        int g = gcd(n, d);
        this.tuple[0] = n / g;
        this.tuple[1] = d / g;

    }

    /* selector */
    private int getNumer(){
        return this.tuple[0];
    }

    /* selector */
    private int getDenom(){
        return this.tuple[1];
    }
    /* Representation ends*/

    /* helper function*/
    private static int gcd(int n, int d){
         if (d == 0)
            return d;
         else
            return gcd(d, n % d);
    }


    /* Use starts */
    public Rational mulRational(Rational x, Rational y){
        return new Rational(x.getNumer()*y.getNumer(), x.getDenom()*y.getDenom());
    }


    public Rational addRational(Rational x, Rational y){
        return new Rational(x.getNumer() * y.getDenom() + y.getNumer() * x.getDenom(), x.getDenom() * y.getDenom());

    }

    /*
     * implementing logical equality but not 
     * reference/shallow_structural/deep_structural equality
     */
    @Override
    public boolean equals(Object obj) {

        return this.getNumer() * ((Rational)obj).getDenom() == ((Rational)obj).getNumer() * this.getDenom();
    }


    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + this.getNumer();
        result = 31 * result + this.getDenom();
        return result;
    }

    @Override
    public String toString() {
        return  this.getNumer() + "/" + this.getDenom();
    }

    /* Use ends */

}

Here, Rational number being compound data (n, d) required the reason to enable Data abstraction which mean: "create barrier between representation and use".

Constructors, selectors and field (in Java) under representation category of above 3 implementations form abstract data type. Because ADT supports an invariant or behaviour condition that:

If we construct rational number x from numerator n and denominator d, then getNumer(x)/getDenom(x) or x.getNumer()/x.getDenom() must equal n/d.

In the above 3 implementations:

  1. Parts of the program that use rational numbers to perform computation use mulRational(..), addRational(..), eqRational(..) && toString(..) computation processes only.

  2. Parts of the program that create rationals use constructor Rational(..) or pair(..) only parts of the program that implement more rational operations like divideRational(..)(say) use getNumer(..) and getDenom(..) only.

  3. Parts of the program that implement constructors use "tuple or list in Python" or "array in Java" or "higher order function in Python (impl2)" and parts of the program implement selectors use "getitem in Python" or "private field in Java" only.

Please correct/improvise the above code if the above implementation looks less elegant for the chosen aspect of "Data abstraction" and "ADT".

\$\endgroup\$
5
  • \$\begingroup\$ Another question, I learnt that data abstraction is built on structured information. So, how do we process un-structured information? \$\endgroup\$ Mar 5 '15 at 9:20
  • 1
    \$\begingroup\$ I don't like to downvote without explaining myself, but it took me a little while to understand why I actually downvoted this. It was determined that A vs B questions are on topic, but I feel like adding C, where C is another language entirely, is too broad and makes for an under-productive question. I think this should have been, minimally, two separate questions. \$\endgroup\$
    – RubberDuck
    Mar 5 '15 at 13:55
  • \$\begingroup\$ @RubberDuck If you see, impl1 and impl2 are with same use and different representations. java code is to provide abstraction using OOP. When you design Data abstraction, abstract data type goes with it. So, what is broad here? \$\endgroup\$ Mar 5 '15 at 14:08
  • 1
    \$\begingroup\$ @overexchange You keep posting broad, nebulous questions about software engineering concepts, to nobody's benefit including your own. I really think you just need to buckle down to acquiring practical experience and come back to asking questions when you have gained both judgement and context. \$\endgroup\$
    – itsbruce
    Mar 5 '15 at 19:44
  • 1
    \$\begingroup\$ The text in your question is exceptionally unclear. For example, in the numbered list of 3 things, why are they numbered? Is it to match up to the numbers of implementations so that point 1 matches implementation 1, or do they apply to all of them? The 3 points seem to be about restrictions to the code, but I can't tell whether they are things you tried to follow while writing the code or something else. If you tried to follow them, you didn't say why. I can't tell what you mean by "chosen aspect" in the last sentence. Are you choosing the aspect? Are we? \$\endgroup\$ Mar 5 '15 at 20:42
5
\$\begingroup\$

I'll comment on your Java implementation:

  1. Your implementation of the equals method is not correct:

    public boolean equals(Object obj) {
        return this.getNumer() * ((Rational)obj).getDenom() == ((Rational)obj).getNumer() * this.getDenom();
    }
    

    If a null is passed to this method, it will throw a NullPointerException. If obj is not an instance of the Rational class, it will throw a ClassCastException. Such behavior violates the contract of the equals method. I would write it this way:

    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof Rational)) {
            return false;
        }
        Rational r = (Rational) o;
        return getNumer() == r.getNumer() && getDenom() == r.getDenom();
    }
    

    Now it returns false in both of these cases instead of throwing an exception.

  2. But it is not the only method of this class which works improperly. An attempt to create any instance ends up with: java.lang.ArithmeticException: / by zero. It the result of an incorrect implementation of the gcd method(which always returns 0). A fixed version:

    private static int gcd(int n, int d){
         if (d == 0)
            return n;
         // ----- ^^^ ------
         // Here we should return n, not d.
         else
            return gcd(d, n % d);
    }
    

    Now it seems to work.

  3. The fact that this code has several easy-to-spot bugs(especially the fact that any constructor call throws) makes me think that you haven't tested this code. I'd recommend picking a habit of using unit-tests. It is a good practice to always test code in an organized, systematic manner.

  4. Now let's switch to the design of this class.

    • I do not see any point in storing a numerator and a denominator in an array. Something like:

      public class Rational {
      
          private int numerator;
          private int denominator;
      
          ...
      }
      

      is easier to understand.

    • Private getters(getNumer and getDenom) look strange. It is simpler to access the numerator and denominator fields directly. It doesn't break encapsulation because they are accessed only from this class, anyway.

  5. Code style:

    • Writing useless comments is a bad practice. There is no need to have a comment before a constructor which says that it is a constructor(the same can be said about a comment before a field which just says that it is a field). Either make them more meaningful or remove them. I would also write a more detailed comments for the equals method. It is not instantly clear what "logical equality" means exactly in this context.

    • Spacing. It is conventional to have a whitespace before an opening curly bracket and around binary operators(it is inconsistent in your code).

  6. Error handling. It makes sense to throw an IllegalArgumentException in the constructor if the denominator argument is 0(it is not a valid rational number so it shouldn't be treated as such).

  7. The barrier between representation and use is not about having a constructor and fields and other methods that perform different operations. The barrier is the Rational class itself. The clients of this class can treat it as a type with a set of defined operations without any knowledge of how it is implemented internally.

\$\endgroup\$
6
  • \$\begingroup\$ With respect to point 4, I feel getNumer() getDenom() is required, if the representation can change from int[] tuple to int numer; int denom; So, this is the reason we are recommending to use selectors in mulRational() method. So, Do you think mulRational() should work on abstract data but not actual data(field)? this will avoid maintenance issue \$\endgroup\$ Mar 5 '15 at 14:02
  • 2
    \$\begingroup\$ @overexchange I cannot think of any other reasonable implementation than two fields(numerator and denominator), but you can keep them if you feel that the representation is likely to change. \$\endgroup\$
    – kraskevich
    Mar 5 '15 at 14:08
  • \$\begingroup\$ With respect to point 7, we wrote public access specifier methods for users, and private access specifier methods for internal representation of data and its behaviour. So, am still not clear How class can be called a barrier when public access api's are public? For user data is like a single unit. \$\endgroup\$ Mar 5 '15 at 14:13
  • \$\begingroup\$ @overexchange That's exactly the point. It provides a set of operations on the data(through public API), but it hides how it works internally. \$\endgroup\$
    – kraskevich
    Mar 5 '15 at 14:18
  • \$\begingroup\$ I would have accessed the data directly using public api, if I had a single data field, something like int xyz; but here we have compound data (n, d). So, representation knows data is compound in nature, user should experience this compound data as a single unit. That is whole point of building data abstractions. For non-compound data, We do not require building data abstraction. for example compound data can be a human profile. \$\endgroup\$ Mar 5 '15 at 14:20

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