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