Data Attribute Notation - Part 3

Data Attribute Notation - Part 3

By Reginald B. Charney

Overload, 7(30):, February 1999


This is the third in the series on Data Attribute Notation (DAN). DAN is an object-oriented coding style that emphasizes data abstraction. It is meant to closely relate abstract concepts as defined in both the analysis and design stages of a project to the project's actual implementation. The intent is to minimize the differences amongst these three stages. This part of the series discusses the equivalence between DAN and designs centered around procedural classes like Michael Schelkin's Abstract Data Interchange classes[ Schelkin ].

Many systems are centered more around actions that need to be taken (action-centric) rather than around the data that needs to be manipulated (data-centric). For example, a messaging system is an action-centric application that invokes a call-back routine when a given event occurs. While the event is a piece of data, the main concern is the message handling. Another example is a dispatching system. Its main concern is effectively using its personnel and inventory resources. Of secondary interest is any single call, individual service person or spare part.

This article tries to unify the treatment of data-centric and action-centric systems.

Action-Centric Systems

An action-centric system uses objects as "wrappers" for procedure code. There is little if any data associated with an object of an Action class. Listing #1 shows an Action class as Michael Schelkin would define it.

class Action
{
public:
  virtual int run() = 0;
};

static int si;

class Reset : public Action
{
public:
  virtual int run() { si = 0; }
};
class EndIt : public Action
{
public:
  virtual int run() { si = -1; }
};

void doIt(Action *ap)
{
  ap->run();
}
...
  Reset *rp = new Reset;
  EndIt *ep = new EndIt;
...
  doIt(rp);  // Reset::run()
...
  doIt(ep);  // EndIt::run()

Listing #1: Action classes

In Part 2 of this series [ Charney ], I discussed programs being composed of fragments that could be combined in various ways. Listing #2 shows a sample using iterators and strings representing parts of programs.

class Reset { };
class Init { /* ... */ };
class Body { /* ... */ };
class Term { /* ... */ };

Init i1("
  Reset r;
  AllPetOwners apo;
  APOiter apoI(apo);
  ");
Init i2("apoI << r;");
Body b2("
  while(Next(apoI))
    cout << apoI << endl;
  ");
Term t1("return 0;");
Fragment f1(i2,b2,Term(""));
Fragment prog(i1,f1,t1);

Listing #2: Fragments Using Strings

As Michael Schelkin pointed out, these strings could be instances of Action classes or their derivatives. Also, initialization and termination code can be implemented using constructors and destructors. So Listing #2 could be rewritten using Action classes as shown in Listing #3.

AllPetOwners *apop;
APOIter *apoIp;
Reset r;

void init1()
{
  apop = new AllPetOwners;
  apoIp = new APOiter(*apop);
}

void term1()
{
  delete apop;
  delete apoIp;
}

void init2() { *apoIp << r; }
void body2()
{
  while(Next(*apoIp))
    cout << *apoIp << endl;
}

class Action
{
public:
  virtual int run() = 0;
};

void doIt(Action& a)
{
  a.run();
}

class Fragment : public Action 
{
  typedef void (*AF)();
  AF i;  // initialization code
  AF b;  // body code
  AF t;  // termination code
public:
  Fragment(AF ii, AF bb, AF tt):
  i(ii), b(bb), t(tt) 
  { if (i) i(); }
  ~Fragment() { if (t) t(); }
  virtual void run()
  { if (b) b(); }
};

// initialize f1 using init2
Fragment f1(init2,body2,Term(0));

// initialize prog using init1
Fragment prog(init1,f1,term1);

doIt(f1);  // run body2
doIt(prog);  // run f1 (ie. body2)

// destroy prog after term1 run
// destroy f1 with no term code

Listing #3: Fragments Using Action Classes

The function doIt() is a simple form of an applicator function . An applicator function takes one or more arguments, one of which is another function that is applied to the remaining applicator arguments. In Listing #3, doIt() invokes the function that is a member of doIt 's argument. There are no other arguments. In Listing #4, the doIt() function is defined to handle Action classes that take arguments.

class X { /* some class */ };
class Y { /* another class */ };

class Act2
{ // 2 arg action
public:
  virtual int run(X& x, Y& y)=0;
};

void doIt(Act2& a2,X& x, Y& y)
{ a2.run(x,y); }

class MyAct2 : public Act2
{
public:
  virtual int run(X& x, Y& y)
  { /* some action */ }
};

MyAct2 a;
X x;
Y y;

doIt(a,x,y);  // a.run(x,y)

Listing #4: doIt() with 2 or 3 Arguments

The advantage of doIt() is any class derived from an Action class invokes the appropriate run() member function because of the virtual function mechanism.

Combining the essense of DAN and action-centric code, we come to the following conclusions:

  • objects are known by their attributes

  • an instance of an attribute can be applied to any object having that attribute

  • an instance of an action class can be applied to an object having that action class an an attribute.

  • if an action is a class then the action class must be an attribute of the object for the action to apply to the object.

  • an action performed on an object affects the state of the object and therefore the value of one or more of its attributes

Questions

Does a pointer to instance of an attribute class fit in with DAN?

If a message handler is an action class, can we say:

  MsgHdlr mh;
  Msg m;
  mh << m;

Can we create a MsgHdlr instance based on type of message that it is meant to handle by defining the instance using constructors with the message as an argument?

  Msg1 m1;
  Msg2 m2;
  MsgHdlr hd1(m1);
  MsgHdlr hd2(m2);

In the examples of feeding a dog meat and walking a dog 5 miles, the first is better handled by a member function while the second can be handled using DAN.

  Meat m;
  Dog d;
  d.feed(m);

  d << Walk(5);

Why?

For example, consider feeding an animal. You can feed meat to a dog and you can feed oats to a horse. Normally, you would define a feed() virtual member function for Dog and one for Horse , each derived from Animal . You would then accept as Food the derived types Meat for Dog s and Oats for Horse s. Thus, you could write the code in Listing #4.

Dog d;  // derived from Animal
Horse h;  // derived from Animal
Meat m;  // derived from Food
Oats o;  // derived from Food

d.feed(m);  // feed meat to dog
h.feed(o);  // feed oats to horse 

Listing #4: feed as member function

However, if we treat actions as if they were classes, then the concept of feed could be represented by the action class Feed . The fragment in Listing #4 could then be written as shown in Listing #5.

Dog d;  // derived from Animal
Horse h;  // derived from Animal
Meat m;  // derived from Food
Oats o;  // derived from Food

d << Feed(m);  // feed dog meat
h << Feed(o);  // feed horse oats

Listing #5: Feed as action class

While the code in Listing #4 is more usual, it requires that the member function feed() for each animal be overloaded for each food type that the animal eats. An alternative would have feed() accept a generic food type like Food , thereby losing the context sensitivity originally sought. That is, we can no longer protect against feeding meat to horses or oats to dogs.

References

[Schelkin] The Object or The Action by Michael V. Schelkin, CVu - The Journal for the C Users Group (UK) Volume 5 Issue 1, November 1992.

[Charney] Data Attribute Notation - Part 2 by Reginald B. Charney - The C++ Journal, Vol 2. No. 2 1992






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.