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:
-
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
- 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
- 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"
- inheritance
- 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:
- Assignment to an expression. <e1> := <e2>
-
An access operation:
- 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
- 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 --------------------
- 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.
-
Messages
The following are allowed.
- Static record as message: The message, or its record structure, is kn own at compile time.
e.g. {Counter inc(X)} {Counter inc(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".
-
Method Definition
The following are possible.
- Fixed argument list:
e.g. meth foo(a:A b:B c:C d:D) … end
- 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
- 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?
- 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.
- Optional Argument
meth foo(a:A b:B<=V) {Browse B} end
- Dynamic method label
meth !A(bar:X) {Browse bar} end
causes the method label to be whatever value the variable A is bound to.
- 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.
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
- B appears in the from declaration in C, ((or))
- 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.
- The inheritance graph is acyclic.
- 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.
- More opportunities for optimizing code.
- Better type-safety.
Disadvantages of Compiled Systems.
- 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:
- 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.
- 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
- 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).
- 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.
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.
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)}
Date: 2011-11-01 14:20:46 IST
HTML generated by org-mode 6.33x in emacs 23