oop

Table of Contents

1 Inheritance

Avoid code duplication. Incremental specification of a data abstraction: class

A class is added as a linguistic abstraction. Syntax of the new class can be derived from old class.

Inheritance is thus a transformation from the set of old classes to a new class.

Whenever one defines a new class, there is now a new way to use it: one can inherit from the class. A very strange advice that's sometimes given: make classes 'final' (uninheritable) by default.

2 Classes as complete data abstractions

Behaviour of an object is specified by a class.

Concepts for defining classes:

  1. Complete Data abstraction define: members, attributes and properties.

    initialization: attributes can be initialized per object: per class:e.g. Java's "static" variables

    dynamic typing: first-class messages first-class attributes

  2. Incremental Data abstraction related to inheritance from and into other classes.

2.1 An Example:

-----With Class-----------              ----classless-----------------
class Counter                           local
        attr val                              proc {Init M S}
        meth init(Value)                           init(Value)=M
          val:=Value                          in
        end                                        (S.val):=Value
                                              end
        meth browse                           proc {Browse_ M S}
          {Browse @val}                            {Browse @{S.val}
        end                                   end
                                              proc {Inc M S}
        meth inc(Value)                            inc(Value) = M
          val:=@val+Value                     in
        end                                        (S.val):=@(S.val)+Value
end                                           end
--------------------------              in
                                              Counter = c(
                                                      attrs:[val]
                                                      methods:m(init:Init
                                                                browse:Browse
                                                                inc:Inc))
                                        end
                                        --------------------------------

Note: This is an executable statement. It

  1. It creates a class value and binds it to the variable "Counter".

The declaration can be put wherever a statement can go. For example, putting the declaration inside a procedure body will create a new class everytime the procedure is invoked. This is useful for parametrized classes.

----Usage-----------------
C = {New Counter init(0)}
{C inc(1)}
{C inc(1)}
{Browse C}
--------------------------

Features in this example: 1. OO Features not used in this example"

  1. inheritance
  2. self.

2.2 Defining classes and objects

Class is a data structure that defines the internal state (attributes), behaviour(methods), the classes it inherits from, and other properties. The data abstraction may be given a partial or a complete implementation.

There can be any number of "instances" of a given class. They are called objects. They have different identities, and have different internal state. Their behaviour is according to the class definition.

An object is created with the operation New:

MyObj = {New MyClass Init}

An object call is similar to a procedure call. It will return when the procedure has finished execution.

2.2.1 Class Members

  • Attributes
    attr : A cell that contains part of the class instance's state.

    Other terms: Instance variable.

    An instance can update its attributes with the following operations:

    1. Assignment to an expression. <e1> := <e2>
    2. An access operation:
    3. Exchange: <e1>:=<e2>

2.2.2 Methods

meth: Procedure that executes in the context of the object, and can access the object's elements. consists of a head: label, and a set of arguments. e.g. method1(V1), and not {Method1 v1}

2.2.3 Initializing Attributes

Attributes can be initialized either

  1. Per instance: This can be done by not initializing in the class declaration.
--------------------
class Address
    attr
       line1   % Per instance
       country % Per instance
    meth init(Country) @country=Country end
end
--------------------
  1. Per class:
--------------------
class EmployeeAddress
    attr
       line1              % Per instance
       company: APEXCORP  % Class attribute
       designation:_      % Class attribute
       country            % Per instance
    meth init(Country) @country=Country end
end
--------------------

Every attribute initialized in the class declaration is a class variable, even if the initial value is unbound (e.g. designation in the above). 2.1 Per brand: This is a special form of per class initialization. A brand is a set of classes that are related through a similar attribute, but they are not necessarily related through inheritance.

----------------------
ACMFP=D.1.1
class Erlang attr:ACMFP end
class Haskell attr:ACMFP end
----------------------

2.2.4 First-class Messages

Invocation of methods is abstractly thought of as "sending a message to the object". We implement messages as records. Thus message heads are patterns that match a record.

  1. Messages The following are allowed.
    1. Static record as message: The message, or its record structure, is kn own at compile time.

e.g. {Counter inc(X)} {Counter inc(1)}

  1. Dynamic record as message: the message is calculated at run-time.

e.g. {Obj M} {Obj {List.toRecord L}} {Obj {Adjoin R X}}

Because messages are records, it is possible to specify arguments "out of order".

  1. Method Definition The following are possible.
    1. Fixed argument list:

e.g. meth foo(a:A b:B c:C d:D) … end

  1. Variable argument list.

e.g. meth foo(a:A b:B c:C …) … end

This is possible because the pattern matching in Oz allows you to specify only part of the features in a record: declare X X = foo(a:1 b:2) foo(b:Y …) = X {Browse Y} % Displays 2

  1. Private method label:

meth A(bar:X) {Browse bar} end

A fresh name is created and assigned when the class is defined. Question: Is the method private to every instance of the class, or is it visible to all instances of the class?

  1. Variable Reference to Method Head

e.g. meth foo(a:A …) = M {Browse M} end

The variable refers to the full message (not the method) as a record. The scope of M is the method body.

  1. Optional Argument

meth foo(a:A b:B<=V) {Browse B} end

  1. Dynamic method label

meth !A(bar:X) {Browse bar} end

causes the method label to be whatever value the variable A is bound to.

  1. Otherwise method

meth Otherwise(M) % Method body end

2.2.5 First-class Attributes

Attribute names can be calculated at run-time.

For example, the following code can set any attribute value.

class Accessor
    meth set(Attribute V)
        A:=X
    end
end

3 Classes as Incremental Data Abstractions

3.1 Inheritance

defines how to construct new classes by extending old ones. We will discuss single inheritance, where every class extends at most one class, and multiple inheritance, where a class can extend more than one class. Keyword: from in the class declaration.

classPoint.oz

Methods and attributes are handled similarly. We discuss methods, as in the text.

If a class C extends a class B, then C is said to be a subclass of B, and B is a superclass of C. In other words, B is a superclass of C if

  1. B appears in the from declaration in C, ((or))
  2. B is a superclass of some class appearing in the from declaration of C.

Methods available in C are defined by a precedence relation called overriding:

A method in C overrides any method with the same label in any superclass of C.

An inheritance hierarchy can be seen as a directed graph. The nodes are the classes. There is an edge from B to C if B is an immediate superclass of C (that is, B appears in the from part of C's declaration.)

A way to think about inheritance: While modelling the hierarchy, think of is-a relationships: Example, A cowboy is-a farm-hand, a farm-hand is an employee and so on. Formally, is-a relationships are called subsumption.

(Caution: Strictly speaking, is-a should be thought of as 'is-a-kind-of' to avoid absurd models as in "Socrates 'is-a' man" - Socrates should be an instance of man, not a subclass.)

3.1.1 Conflicts due to Multiple inheritance

A hierarchy is legal if it meets two conditions.

  1. The inheritance graph is acyclic.
  2. After striking out the overridden methods, each remaining method should have a unique label and be defined only once in the hierarchy.

It is possible to violate condition 2 in multiple inheritance.

e.g. ArtisticCowboy.oz

Even though this can be caught at compile time, Oz doesn't do so. However, this definition will cause a conflict in the run-time object system.

This is the main reason why some OO languages forbid multiple inheritance. It is still a useful approach to design. Java, instead provides "interfaces" - these are empty declarations of functions to be implemented later - carefully avoiding the same functions being defined mutliple times, even if they are declared multiple times.

3.1.2 Compile-time vs. Runtime

When a class is compiled (any declaration is an executable statement), it creates a class, which is a value in the language. This class value can be passed to New to create an object.

Mozart environment of Oz does not distinguish between compile time and run-time

Advantages of Compiled Systems.

  1. More opportunities for optimizing code.
  2. Better type-safety.

Disadvantages of Compiled Systems.

  1. Lesser flexibility, for genericity (passing higher-order procedures as arguments) and instantiation (procedures as return values)

3.2 Method Access Control (Static and Dynamic Binding)

When executing inside an object, we may want to call another method inside the same object. This is tricky with inheritance. There are two settings:

3.2.1 Dynamic Binding

class Account
    attr balance:0
    meth transfer(Amt)
        balance:=@balance+Amt
    end
    meth batchTransfer(AmtList)
        for A in AmtList do
            {self transfer(A)} % Dynamically binds to current object
        end
    end
end

class LoggedAccount from Account
   meth transfer(Amt)
       {Browse Amt}          % Log the transaction
       Account,transfer(Amt) % superclass method - Static Binding
   end
end

In the above example, suppose we create an object of type LoggedAccount, and call {LoggedAccount batchTransfer(1 2 ~3)}. The method executing is defined in the superclass. However, because we used self, the transfer method calls the LoggedAccount transfer method.

For example, the call stack in the initial call of transfer would be:

{LoggedAccount batchTransfer([1 2 ~3])} ->
{Account batchTransfer([1 2 ~3])}  ->
{self transfer(1)} === {LoggedAccount transfer(1)}

This really emphasizes OO design: dynamic binding allows the possibility that an Account can be extended via inheritance, and the new behaviour is enabled in the extended class.

Dynamic Binding chooses the method with the matching label that is visible in the current object.

Dynamic binding is the only possible behavior for attributes.

3.2.2 Static Binding

However, within the new class, suppose we want to add some new behaviour, and then call the superclass behaviour. Then we can specify which method to invoke by the format

ClassName, method

3.3 Encapsulation Control (Public, Private, Protected)

Each member of a class is defined within a scope. The scope is that part of the program where the member is visible. Procedural programming has two main kinds - static and dynamic.

Inheritance encapsulation introduces new scopes - usually called public, private and protected. This aspect of OO languages is usually called visibility.

In Oz, defaults: All methods are public, all attributes are private.

CAUTION: Private means different concepts in different OO languages.

          Visibility of Private
          =====================

     .........
     . +----+.
     . | C  |. Vertical: Smalltalk, Oz
     . +----+.
     .   |   .
     . +----+.
     . | D  |.
     . +----+.
.........|................
.    . +----+.           . Horizontal: Java, C++
. +--.-| E  |.-----+     .
. |  . +----+.     |     .
. |  .   |   .     |     .
.(E1).  (E2) .    (E3)   .
..........................

E is a subclass of D, which is a subclass of C.

In Oz:

  1. a private member is one which is visible only in the object instance. The object can see all members defined in its class, and any of its superclasses.
  2. a public member is one which is visible anywhere in the program.

3.3.1 Constructing other scopes

To support scopes as in C++ and JAVA, we use method heads be name values rather than atoms. A name is an unforgeable constant. This allows the class to pass references to its members in a controlled way.

  • Private methods ( a la C++ and JAVA)
    When a method head is a name value, its scope is limited to all instances of the class, but not to instances of its subclasses. This is private visibility in the sense of C++ and JAVA.
    class MyClass
        meth M(X) % Method with variable identifier as head
            skip
        end
    end
    
    1. If the method name is a variable identifier, when the class is compiled, a name is created and binds to the variable. This means that any instance of the class can call method A in any instance of MyClass. (This is what is surprising about JAVA's private visibility).
    2. If the method name is an escaped variable identifer, it means that we will declare and bind the variable identifiers outside the class.
    local 
       A='myMethod'
    in
       class C
           meth !A(X) % Escaped variable identifier
               skip
           end
       end
    end
    
  • Protected Method (C++ sense)
    A protected member in C++ is one which is visible to instances of the class, and to any instance of its subclass. (How is this different from private visibility in Oz?)

    Using names and attributes, we can simulate protected methods. Recall that attributes are visible to subclass instances, but not to arbitrary objects.

    class C
        attr pa:M           % reference to a 'private' method
        meth M(X) skip end  % 'private' method (see last section)
    end
    
    class D from C
        meth b(...) 
            A = @pa         % Accessing the superclass's M method
        in
            {self A(5)}
        end
    end
    

4 Forwarding and Delegation

Inheritance is one way to definenew behaviour. Recall that it models an 'is-a' relationship.

We can also model composite objects, without inheritance. These model 'has-a' relationships. The idea is that a composite object O "contains" a simpler object S. O handles some messages, and those it cannot handle, it passes on to S to handle. This is where otherwise methods are useful.

For example, we may have a class Window for a graphics utility, which provides, a support for handling a 'zoom' message (among others). Now, suppose we add a scrollbar for this window. We can construct a composite object, ScrollBarWindow, which handles scrolling by itself, and passes on all other messages to the Window object contained in it.

class Window
    meth zoom() skip end
    meth start() skip end
end

class ScrollbarWindow
    attr w
    meth init() w:={New Window start}
    meth scroll(X)
         % Scroll window
    end
    meth otherwise(...)=Msg
        {@w Msg}
    end
end

There are two ways to pass on messages to subobjects: forwarding and delegation.

4.1 Forwarding

An object can forward to any other object. This can be implemented with the otherwise(M) method. The argument M is a first-class message that can be passed to other objects.

The following code gives a constructor NewF. Objects created with NewF have a method setForward(ForwardedObject), which lets them set an object dynamically to which it can send messages that it does not understand.

Forward.oz

class GraphicsTool
        meth init skip end
        meth zoom() skip end
end

class Window
        meth init skip end
        meth scroll() skip end
end

GraphicsObj = {NewF GraphicsTool init}
WindowObj   = {NewF Window init}
{WindowObj setForward(GraphicsObj)}

4.2 Delegation

This is similar to forwarding. However, there is a difference. In forwarding, self will change to ForwardedObject once we forward the message to it. In delegation, the self is always the object that initiates the forwarding.

This can be seen, therefore, as a way of dynamically creating inheritance hierarchies among objects rather than among classes.

Delegate.oz

class C1
        attr i:0        % Class variable

        meth init skip end

        meth inc(Increment) 
                {@this set(i {@this get(i $)}+Increment)}
        end

        meth browse
                {@this inc(10)}
                {Browse c1#{@this get(i $)}}
        end

        meth c
                {@this browse}
        end
end

class C2
        attr i:0        % Class variable
        meth init skip end 

        meth browse
                {@this inc(100)}
                {Browse c2#{@this get(i $)}}
        end
end

% Creating the objects. Both have to be created with NewD.
Obj1 = {NewD C1 init} 
Obj2 = {NewD C2 init} 
{Obj2 setDelegate(Obj1)}

% Calling a delegated method
{Obj2 call(c)}

5 Reflection

Reflective systems an inspect part of its execution state when it is running. This is especially powerful in the case of object-oriented systems. Reflection helps you to inspect and even change the inheritance hierarchy at runtime.

The description of how the object system works is called the meta object protocol. Reflection allows you to modify it.

Question: How would you use reflection to support a generalized "toString"?

5.1 Method Wrapping

fun {TracedNew Class Init}
        Obj = {New Class Init}
        
        TInit = {NewName}

        class Tracer
                meth !TInit skip end
                meth otherwise(M) 
                        {Browse entering({Label M})}
                        {Obj M}
                        {Browse leaving({Label M})}
                end
        end
in
        {New Tracer Tinit}
end

% Usage
TracedObj = {TracedNew Class Init}
{TracedObj hello}

5.2 Reflection of Object State

The following class in Oz supports reflection of the entire state of an object.

ObjectSupport.reflect

Inheriting from this class gives the following three methods.

clone(X)
toChunk(X)
fromChunk(X)

% Usage
C1 = {New Counter init(0)}
C2 = {New Counter init(10)}
{C1 toChunk(X)}
C2 ={C2 fromChunk(X)}

Author: Satyadev Nandakumar <satyadev@cse.iitk.ac.in>

Date: 2011-11-01 14:20:46 IST

HTML generated by org-mode 6.33x in emacs 23