---- Answers to sample questions to prepare for the final exam ----
1. Given the following:
interface I
{
I f();
}
class A : I
{
public virtual I f() { return this; }
}
Explain what's wrong with the following lines:
A x = new A();
A y = x.f();
>>>
The second line will not compile because the return type of x.f() is I,
not A. Explicit casting is required from subclass to superclass.
If we had written A y = (A)x.f(), then it would compile since
A is a subclass of I. It would also run in this case since x is indeed
an A object.
2. In the visitor pattern we had a critical function:
public object accept(visitor v) { return v.visit(this); }
Where was this function defined (in what class(es) does it exist?
visitor or visitee?)
What does "this" refer to?
>>>
The function is placed in every visitee subclass. "this" refers to
the visitee, the data object to be visited. A subtle point about this
function is that, if you look at our examples such as the food visitors,
you'll see that there's no accept in the "foodbase" superclass. Why
not put it in the base superclass, and just have every subclass inherit it?
It's because of "this" - the type of "this" in foodbase would be foodbase,
and not one of the subclasses (meat, fruit, vegetable). The visitor objects
can only visit one of these specific subclasses.
3. Describe a situation in which an object adapter (aka adapter pattern)
can be useful. Can you think of a situation outside of the food
program?
>>>
When you have two classes that have incompatible interfaces, and want
to consider one type of object as another type of object. For
example:
lets say you have
Interface POINT
{
int xcord();
int ycord();
}
class point : POINT
{ int x, y;
int xcord() {return x;}
int ycord() {return y;}
}
but the following class is not related:
class circle
{
int x, y, radius;
}
Now suppose somewhere you have a function
public static double distance(POINT A, POINT B)
{
int dx = A.xcord() - B.xcord();
int dy = A.ycord() - B.ycord();
return Math.Sqrt(dx*dx+dy*dy);
}
Suppose now you wish to call the distance function on circles instead
of points. You can't do it directly because the distance function
expects points, not circles. In order to avoid having to rewrite the
distance function, you must ADAPT a circle to a point:
class pointadapter : Point
{
private circle c;
public pointadapter(circle x) { c=x; }
int xcord() { return c.x; }
int ycord() { return c.y; }
}
The adapter must implement all the functions of the interface it is adapting
an object to.
Now to find the distance between two circels c1, and c2, you can call
... distance(new pointadapter(c1), new pointadapter(c2));
4. Explain the difference between natural and artificial polymorphism.
Give an example of each.
>>>
Polymorphism is first of all a characteristic of the algorithm, not of
the program or programming language. Some algorithms, such as finding
the length of a linked list, are polymorphic by nature since it never
looks at what's stored in the linked lists. Some algorithms, such as
sorting a list, also have the potential to work for many kinds of
lists, but only if we supply the appropriate definition of what it
means for two elements to be "<" and "==" to eachother. This kind of
procedure are artificially polymorphic. Programming languages have
mechanisms that allow you to build this kind of abstraction.
Inheritance is one method. One can define different procedures for
"<" and "==" in subclasses. In "simpler" languages such as Scheme,
Perl or pure C, one can achieve this kind of polymorphism by passing a
function parameter to the procedure. For example, one that defines
"<" for a sorting procedure.
Please understand this by putting it in your own words. Don't just
memorize it. See question 6 below (hint: it's asking the same question,
but from a different angle).
5. Explain in your own words one advantage of parametric polymorphism
over inheritance polymorphism. That is, what's the difference between
using a type parameter , and calling your variables (for example)
"A x" instead of "object x".
>>>
The basic problem with the inheritance approach to polymorphism is that
we loose the ability to type check code at compile time. As you should
know well by now, many errors are only runtime errors, not compile time
errors. At compile time, all that is known is the superclass of a variable,
not its actual type. If we type a variable as "object", then it could be
anything and we effectively loose all ability to catch type errors statically.
Generics, or parametric types, give us the ability to type check code
at compile time. That is, we define a parametric class such as
class yourclass
{ ...
but when we use it, we have to instantiate it with an actual type, as in
yourclass x = ...
yourclass y = ...
Thus the compiler can see the specific type information for each
object. Parametric types are not always better than inheritance,
since inheritance lets you define different versions of procedures for
different subtypes. But sometimes we use "object x" to mean that x can
be of any type, and this is really not the right use of inheritance.
Another advantage of generics over the "object" supertype is speed. There
is no runtime type casting involved, and there is also no need to "box and
unbox", that is, convert between primitive and reference types.
5b. Assume that classes sub1 and sub2 both extend class super1. Consider the
following alternatives for defining a polymorphic linked list class
that can contain elements of either sub1 or sub2:
class one
{
super1 item; // head, car
one next; // tail, cdr
}
class two where A : super1
{
A item;
two next;
}
Assume you wish to have a linked list M of sub1 objects and another list
N of sub2 objects. Which class would you use and why?
Assume you wish to have a single list that consists of some sub1 objects
and some sub2 objects. Which class would you use?
--
Since the two lists will only contain objects of the same type,
I would use the parametric version for better error reporting and
better efficiency (due to lack of run-time type casts and checks).
two m = new two();
two n = new two();
two n2 = new two(); n.next = n2;
However, if the lists can contain mixed elements, or if it is desired
that the type of referenced objects can change from one type to another,
then I would use the first method.
one v = new one();
v.item = "hello"; // a string is an object two
v.next = new one(); // link to another list
v.next.item = new bankaccount(100); // different type of object
One can also have this type of list using class two by instantiating
A with object: two