Wednesday, July 30, 2014

"Private" App Class Members

I was reading Lee Greffin's post More Fun with Application Packages -- Instances and stumbled across this quote from PeopleBooks:

A private instance variable is private to the class, not just to the object instance. For example, consider a linked-list class where one instance needs to update the pointer in another instance.

What exactly does that mean? I did some testing to try and figure it out. Here is what I came up with:

  1. It is still an instance variable which means each in-memory object created from the App Class blue print has its own memory placeholder for each instance member.
  2. Instances of other classes can't interact with private instance members.
  3. Instances of the exact same class CAN interact with private members of a different instance.
  4. Private instance members differ from static members in other languages because they don't all share the same pointer (pointer, reference, whatever).

I thought it was worth proving so here is my sample. It is based on the example suggested in PeopleBooks:

For example, consider a linked-list class where one instance needs to update the pointer in another instance.

The linked list is just an item with a pointer to the next item (forward only). A program using it keeps a pointer to the "head" and then calls next() to iterate over the list. It is a very common pattern so I will forgo further explanation. Here is a quick implementation (in the App Package JJM_COLLECTIONS):

class ListItem
   method ListItem(&data As any);
   method linkTo(&item As JJM_COLLECTIONS:ListItem);
   method next() Returns JJM_COLLECTIONS:ListItem;
   method getData() Returns any;
private
   instance JJM_COLLECTIONS:ListItem &nextItem_;
   instance any &data_;
end-class;

method ListItem
   /+ &data as Any +/
   %This.data_ = &data;
end-method;

method linkTo
   /+ &item as JJM_COLLECTIONS:ListItem +/
   &item.nextItem_ = %This;
end-method;

method next
   /+ Returns JJM_COLLECTIONS:ListItem +/
   Return %This.nextItem_;
end-method;

method getData
   /+ Returns Any +/
   Return %This.data_;
end-method;

Notice the linkTo method sets the value of the private instance member of a remote instance (its parameter), NOT the local instance. This is what is meant by private to the class, not private to the instance. Each instance has its own &nextItem_ instance member and other instances of the exact same class can manipulate it. Here is the test case I used to test the remote manipulation implementation:

import TTS_UNITTEST:TestBase;
import JJM_COLLECTIONS:ListItem;

class TestListItem extends TTS_UNITTEST:TestBase
   method TestListItem();
   method Run();
end-class;

method TestListItem
   %Super = create TTS_UNITTEST:TestBase("TestListItem");
end-method;

method Run
   /+ Extends/implements TTS_UNITTEST:TestBase.Run +/
   Local JJM_COLLECTIONS:ListItem &item1 =
      create JJM_COLLECTIONS:ListItem("Item 1");
   Local JJM_COLLECTIONS:ListItem &item2 =
      create JJM_COLLECTIONS:ListItem("Item 2");
   
   &item2.linkTo(&item1);
   
   %This.AssertStringsEqual(&item1.next().getData(), "Item 2",
      "The next item is not Item 2");
   %This.Msg(&item1.next().getData());
end-method;

The way it is written requires you to create the second item and then call the second item's linkTo method to associate it with the head (or previous) element.

Now, just because you CAN manipulate a private instance member from a remote instance doesn't mean you SHOULD. Doing so seems to violate encapsulation. You could accomplish the same thing by reversing the linkTo method. What if we flipped this around so you created the second item, but called the first item's linkTo? It is really the first item we want to manipulate in a forward only list (now, if it were a multi-direction list perhaps we would want to manipulate the &prevItem_ member?). Here is what the linkTo method would look like:

method linkTo
   /+ &item as JJM_COLLECTIONS:ListItem +/
   %This.nextItem_ = &item;
end-method;

Now what if we wanted a forward AND reverse linked list? Here is where maybe the ability to manipulate siblings starts to seem a little more reasonable (I still think there is a better way, but humor me):

class ListItem
   method ListItem(&data As any);
   method linkTo(&item As JJM_COLLECTIONS:ListItem);
   method next() Returns JJM_COLLECTIONS:ListItem;
   method prev() Returns JJM_COLLECTIONS:ListItem;
   method remove() Returns JJM_COLLECTIONS:ListItem;
   method getData() Returns any;
private
   instance JJM_COLLECTIONS:ListItem &nextItem_;
   instance JJM_COLLECTIONS:ListItem &prevItem_;
   instance any &data_;
end-class;

method ListItem
   /+ &data as Any +/
   %This.data_ = &data;
end-method;

method linkTo
   /+ &item as JJM_COLLECTIONS:ListItem +/
   REM ** manipulate previous sibling;
   &item.nextItem_ = %This;
   %This.prevItem_ = &item;
end-method;

method next
   /+ Returns JJM_COLLECTIONS:ListItem +/
   Return %This.nextItem_;
end-method;

method prev
   /+ Returns JJM_COLLECTIONS:ListItem +/
   Return %This.prevItem_;
end-method;

method remove
   /+ Returns JJM_COLLECTIONS:ListItem +/
   %This.nextItem_.linkTo(%This.prevItem_);
   REM ** Or manipulate both siblings;
   REM %This.prevItem_.nextItem_ = %This.nextItem_;
   REM %This.nextItem_.prevItem_ = %This.prevItem_;
   Return %This.prevItem_;
end-method;

method getData
   /+ Returns Any +/
   Return %This.data_;
end-method;

And here is the final test case

import TTS_UNITTEST:TestBase;
import JJM_COLLECTIONS:ListItem;

class TestListItem extends TTS_UNITTEST:TestBase
   method TestListItem();
   method Run();
end-class;

method TestListItem
   %Super = create TTS_UNITTEST:TestBase("TestListItem");
end-method;

method Run
   /+ Extends/implements TTS_UNITTEST:TestBase.Run +/
   Local JJM_COLLECTIONS:ListItem &item1 =
      create JJM_COLLECTIONS:ListItem("Item 1");
   Local JJM_COLLECTIONS:ListItem &item2 =
      create JJM_COLLECTIONS:ListItem("Item 2");
   Local JJM_COLLECTIONS:ListItem &item3 =
      create JJM_COLLECTIONS:ListItem("Item 3");
   
   &item2.linkTo(&item1);
   
   %This.AssertStringsEqual(&item1.next().getData(), "Item 2",
      "Test 1 failed. The next item is not Item 2");
   %This.AssertStringsEqual(&item2.prev().getData(), "Item 1",
      "Test 2 failed. The prev item is not Item 1");
   
   &item3.linkTo(&item2);
   %This.AssertStringsEqual(&item1.next().next().getData(), "Item 3",
      "Test 3 failed. The next.next item is not Item 3");
   %This.AssertStringsEqual(&item1.next().next().prev().getData(), "Item 2",
      "Test 4 failed. The prev item is not Item 2");
   
   Local JJM_COLLECTIONS:ListItem &temp = &item2.remove();
   %This.AssertStringsEqual(&item1.next().getData(), "Item 3",
      "Test 5 failed. The next item is not Item 3");
   %This.AssertStringsEqual(&item1.next().prev().getData(), "Item 1",
      "Test 6 failed. The prev item is not Item 1");
   
end-method;

I hope that helps clear up some of the confusion around the term "private" as it relates to Application Classes.

2 comments:

Lee Greffin said...

Thanks first of all for referencing my blog!

In the Java literature one reason I see for a static field is the idea of a counter/incrementer for invoicing.

An initial object is instantiated, setting the value of a static field to 1. Each subsequent instantiation gets the value from the base class - and then increments by one. Probably more useful in a Java Bean environment.

Note this allows for automatic creation of a counter/invoice number/id what have you - without having to do a read/right to a file or disc.

Here's a discussion of the same in Stack Overflow: http://stackoverflow.com/questions/16716646/how-to-increment-a-field-member-in-a-java-class-each-time-a-new-one-is-instantia

Jim Marion said...

@Lee, exactly, and I wish PeopleCode had that functionality. It does not. Another great use case is strong typed enums. You can create enums with PeopleCode, but each one is a new instance and has a duplicate copy of likely hard coded values. I found this to be annoying when I was trying to wrap log4j in PeopleCode classes.