|
Chapter 25: Object-Oriented Programming25.1 Background and TerminologyIn this chapter "OOP" will stand for "object-oriented programming". Here is the barest thumbnail sketch of OOP.On occasion, a program needs to build, maintain and use a collection of related data, where it is natural to consider the collection to be, in some sense, a whole. For example, a "stack" is a sequence of data items, such that the most-recently added item is the first to be removed. If we intend to make much use of stacks, then it might be a worthwhile investment to write some functions dedicated to building and using stacks. The combination of some data and some dedicated functions is called an object. Every object belongs to some specific class of similar objects. We will say that a stack is an object of the Stack class. The dedicated functions for objects of a given class are called the "methods" of the class. For example, for objects of the Stack class we will need a method for adding a new item, and a method for retrieving the last-added item. An object needs one or more variables to represent its data. Such variables are called fields. Thus for a stack we may choose to have a single field, a list of items. In summary, OOP consists of identifying a useful class of objects, and then defining the class by defining methods and fields, and then using the methods. By organizing a program into the definitions of different classes, OOP can be viewed as a way of managing complexity. The simple examples which follow are meant to illustrate the machinery of the OOP approach, but not to provide much by way of motivation for OOP. We will be using a number of library functions, mostly with names beginning "co", meaning "class and object". A brief summary of them is given at the end of this chapter. 25.2 Defining a Class25.2.1 Introducing the ClassFor a simple example, we look at defining a class which we choose to call "Stack". A new class is introduced with the library function coclass.coclass 'Stack'coclass is used for its effect, not its result. The effect of coclass is to establish and make current a new locale called Stack. To verify this, we can inspect the name of the current locale: coname '' +-----+ |Stack| +-----+ 25.2.2 Defining the MethodsA new object comes into being in two steps. The first step uses library verb conew to create a rudimentary object, devoid of fields, a mere placeholder. The second step gives a new object its structure and initial content by creating and assigning values to the field-variables.We will deal with the first step below. The second step we look at now. It is done by a method conventionally called create (meaning "create fields", not "create object"). This is the first of the methods we must define. For example, we decide that a Stack object is to have a single field called items, initially an empty list. create =: 3 : 'items =: 0 $ 0'The connection between this method and the Stack class is that create has just been defined in the current locale, which is Stack. This create method is a verb. In this example, it ignores its argument, and its result is of no interest: it is executed purely for its effect. Its effect will be that the (implicitly specified) object will be set up to have a single field called items as an empty list. Our second method is for pushing a new value on to the front of the items in a stack. push =: 3 : '# items =: (< y) , items'The push method is a verb. Its argument y is the new value to be pushed. We made a design-decision here that y is to be boxed and then pushed. The result is of no interest, but there must be some result, so we chose to return (# items) rather than just items. Next, a method for inspecting the "top" (most-recently added) item on the stack. It returns that value of that item. The stack is unchanged. top =: 3 : '> {. items'Next a method to remove the top item of the stack. pop =: 3 : '# items =: }. items'Finally, a method to "destroy" a Stack object, that is, eliminate it when we are finished with it. For this purpose there is a library function codestroy. destroy =: codestroyThis completes the definition of the Stack class. Since we are still within the scope of the coclass 'Stack' statement above, the current locale is Stack. To use this class definition we return to our regular working environment, the base locale. cocurrent 'base' 25.3 Making New ObjectsNow we are in a position to create and use Stack objects. A new Stack is created in two steps. The first step uses the library verb conew.S =: conew 'Stack'The result of conew which we assigned to S is not the newly-created object itself. Rather, the value of S is in effect a unique reference-number which identifies the newly-created Stack object. For brevity we will say "Stack S" to mean the object referred to by S. Stack S now exists but its state is so far undefined. Therefore the second step in making the object is to use the create method to change the state of S to be an empty stack. Since create ignores its argument, we supply an argument of 0 create__S 0Now we can push values onto the stack S and retrieve them in last-in-first-out order. In the following, the expression (push__S 'hello' means: the method push with argument 'hello' applied to object S. push__S 'hello' 1 push__S 'how are you?' 2 push__S 'goodbye' 3 pop__S 0 2 top__S 0 how are you? 25.3.1 Dyadic ConewThe two steps involved in creating a new object, conew followed by create, can be collapsed into one using dyadic conew. The scheme is that:o =: conew 'Class' create__o argcan be abbreviated as: o =: arg conew 'Class'That is, any left argument of conew is passed to create, which is automatically invoked. In this simple Stack class, create ignores its argument, but even so one step is neater than two. For example: T =: 0 conew 'Stack' push__T 77 1 push__T 88 2 top__T 0 88 25.4 Listing the Classes and ObjectsIn this section we look at inspecting the population of objects and classes we have created. The expression (18!:1) 0 1 produces a list of all existing locales.(18!:1) 0 1 +-+-+-----+----+----+-+-------+--------+------+-----+-+ |0|1|Stack|base|ctag|j|jadetag|jcompare|jregex|jtask|z| +-+-+-----+----+----+-+-------+--------+------+-----+-+We see here the names of locales of 3 different kinds. Firstly, there are ordinary locales such as base, and z, described in Chapter 24. These are created automatically by the J system. Depending on the version of J you are using, you may see a list different from the one shown here. Secondly, there are locales such as Stack. The Stack locale defines the Stack class. If we view this locale (with the view utility function from Chapter 24) view 'Stack' IP =: 1 create =: 3 : 'items =: 0 $ 0' destroy =: codestroy pop =: 3 : '# items =: }. items' push =: 3 : '# items =: (< y) , items' top =: 3 : '> {. items'we see a variable IP (created automatically) and our methods which we defined for Stack. Thirdly, we have locales such as 0. Here the name is a string of numeric digits (that is, '0'). Such a locale is an object. The variable S has the value <'0', so that here object S is locale '0'.
We see a variable COCREATOR, which identifies this locale as an object, and the field(s) of the object. The path from an object is given by the verb 18!:2 18!:2 S +-----+-+ |Stack|z| +-----+-+Since S is a Stack object, the first locale on its path is Stack. Recall from Chapter 24 that, since S = <'0' then the expression push__S 99 means:
obcl =: 3 : '(, ({. @: (18!:2)))"0 (18!:1) 1'Currently we have variables S and T each referring to a Stack object.
(Again, depending on the version of J you are using, you may see further objects and classes automatically generated by the J system for its own use.) A Stack, S say, can be removed using the destroy method of the Stack class. destroy__S '' 1We see it has gone. obcl '' +-+-----+ |1|Stack| +-+-----+In conclusion, note particularly that an object is a locale. This means that an object is not a variable or a value. An object is not something which can be an argument to a function, or returned by a function. An object cannot be part of another object. All these things can be done using names of objects in place of objects. Every object has a unique system-generated name, represented by a string. This string is just an ordinary value, and all the usual things can be done with it. 25.5 InheritanceHere we look at how a new class can build on an existing class. The main idea is that, given some class, we can develop a new class as a specialized version of the old class.For example, suppose there is a class called Collection where the objects are collections of values. We could define a new class where, say, the objects are collections without duplicates, and this class could be called Set. Then a Set object is a special kind of a Collection object. In such a case we say that the Set class is a child of the parent class Collection. The child will inherit the methods of the parent, perhaps modifying some and perhaps adding new methods, to realize the special properties of child objects. For a simple example we begin with a parent-class called Collection, coclass 'Collection' create =: 3 : 'items =: 0 $ 0' add =: 3 : '# items =: (< y) , items' remove =: 3 : '# items =: items -. < y' inspect =: 3 : 'items' destroy =: codestroyHere the inspect method yields a boxed list of all the members of the collection. A quick demonstration: cocurrent 'base' C1 =: 0 conew 'Collection' add__C1 'foo' 1 add__C1 37 2 remove__C1 'foo' 1 inspect__C1 0 +--+ |37| +--+Now we define the Set class, specifying that Set is to be a child of Collection with the library verb coinsert. coclass 'Set' coinsert 'Collection'To express the property that a Set has no duplicates, we need to modify only the add method. Here is something that will work: add =: 3 : '# items =: ~. (< y) , items'All the other methods needed for Set are already available, inherited from the parent class Collection. We have finished the definition of Set and are ready to use it. cocurrent 'base' s1 =: 0 conew 'Set' NB. make new Set object. add__s1 'a' 1 add__s1 'b' 2 add__s1 'a' 2 remove__s1 'b' 1 inspect__s1 0 NB. should have just one 'a' +-+ |a| +-+ 25.5.1 A Matter of PrincipleRecall the definition of the add method of class Set.add_Set_ 3 : '# items =: ~. (< y) , items'It has an objectionable feature: in writing it we used our knowledge of the internals of a Collection object, namely that there is a field called items which is a boxed list. Now the methods of Collection are supposed to be adequate for all handling of Collection objects. As a matter of principle, if we stick to the methods and avoid rummaging around in the internals, we hope to shield ourselves, to some degree, from possible future changes to the internals of Collection. Such changes might be, for example, for improved performance. Let's try redefining add again, this time sticking to the methods of the parent as much as possible. We use our knowledge that the parent inspect method yields a boxed list of the membership. If the argument y is not among the membership, then we add it with the parent add method. add_Set_ =: 3 : 0 if. (< y) e. inspect 0 do. 0 else. add_Collection_ f. y NB. see below ! end. )Not so nice, but that's the price we pay for having principles. Trying it out on the set s1: inspect__s1 0 +-+ |a| +-+ add__s1 'a' 0 add__s1 'z' 2 inspect__s1 0 +-+-+ |z|a| +-+-+ 25.6 Using Inherited MethodsLet us review the definition of the add method of class Set.add_Set_ 3 : 0 if. (< y) e. inspect 0 do. 0 else. add_Collection_ f. y NB. see below ! end. )There are some questions to be answered. 25.6.1 First QuestionHow are methods inherited? In other words, why is the inspect method of the parent Collection class available as a Set method? In short, the method is found along the path, that is,
copath > s1 +---+----------+-+ |Set|Collection|z| +---+----------+-+ 25.6.2 Second QuestionIn the definition of add_Set_add_Set_ 3 : 0 if. (< y) e. inspect 0 do. 0 else. add_Collection_ f. y NB. see below ! end. )Given that the parent method inspect is referred to as simply inspect, why is the parent method add referred to as add_Collection_? Because we are defining a method to be called add and inside it a reference to add would be a fatal circularity. 25.6.3 Third Questionwhy is the parent add method specified as add_Collection_ f. ?Because add_Collection_ is a locative name, and evaluating expressions with locative names will involve a change of locale. Recall from Chapter 24 that add_Collection_ 0 would be evaluated in locale Collection, which would be incorrect: we need to be in the object locale when applying the method. Since f. is built-in, by the time we have finished evaluating (add_Collection_ f.) we are back in the right locale with a fully-evaluated value for the function which we can apply without change of locale. add_Collection_ f. 3 : '# items =: (< y) , items' 25.7 Library VerbsHere is a brief summary of selected library verbs.
This brings us to the end of Chapter 25 |
The examples in this chapter
were executed using J version 802 beta.
This chapter last updated 30 Jun 2014
Copyright © Roger Stokes 2014.
This material may be freely reproduced,
provided that acknowledgement is made.