top of page
clopisrepsaukodi

Object-Oriented callback design: How to test and debug callbacks effectively



Design patterns not only accelerate the design phase of an object-oriented (OO) project but also increase the productivity of the development team and quality of the software. A Command pattern is an object behavioral pattern that allows us to achieve complete decoupling between the sender and the receiver. (A sender is an object that invokes an operation, and a receiver is an object that receives the request to execute a certain operation. With decoupling, the sender has no knowledge of the Receiver's interface.) The term request here refers to the command that is to be executed. The Command pattern also allows us to vary when and how a request is fulfilled. Therefore, a Command pattern provides us flexibility as well as extensibility.


In programming languages like C, function pointers are used to eliminate giant switch statements. (See "Java Tip 30: Polymorphism and Java" for a more detailed description.) Since Java doesn't have function pointers, we can use the Command pattern to implement callbacks. You'll see this in action in the first code example below, called TestCommand.java.




Object-Oriented callback design



Developers might use a switch statement with a case for each command. Usage of Switch statements during coding is a sign of bad design during the design phase of an object-oriented project. Commands represent an object-oriented way to support transactions and can be used to solve this design problem.


Callbacks are tremendously useful in object-oriented design when one needs to decouple two classes but let them be connected via a single function call. Examples abound and at some point I'll expand. For now, this is a place for me to keep my experiments with various approaches to implementing callbacks in C++.


This approach takes advantage of inheritance and polymorphism to implement a callback. This works especially well if the callback interface requires more than one function. The problem with this approach is when we want to connect a single callee to a number of instances of the same caller. As an example, if we want to create three or four timers and handle each one in a different callback function (e.g. onOneSecondTimer(), onTwoSecondTimer(), ...). Since the interface class forces us to use a specific function name, we cannot do this.


In C, function pointers are the easiest way to implement callbacks and they can be made to work in a C++ class, although it is a little awkward. We'll need two functions to pull this off. The first is a static callback function, and the second is a member callback function.


For use in C++ code, this is a fairly cumbersome approach. You've got to define two functions for every callback: the static function and the actual callback function. When interfacing with C code that uses function pointers for callbacks, this is a perfect approach. It successfully makes the jump from C to C++.


C++11 introduces many handy new features to the language. One of them is lambda functions. A lambda function is an anonymous, temporary, usually small function. By combining the new std::function with a lambda function that looks very similar to the above function pointer approach, we can have a pretty decent callback mechanism.


If you aren't familiar with lambda functions in C++, here's a quick breakdown. "[&callee]" says to "capture" callee by reference. This basically means to make the variable "callee" available within the lambda function. We do this by reference to avoid a copy which would be disastrous. "(int i)" says that our lambda function takes a single int parameter. This matches callbackFunction(). Within the braces is the code for the lambda function. In this case we simply delegate the call to callbackFunction(). Note that we do not specify the return type of our lambda function because C++11 is clever and will figure it out automatically.


I would say that Rich Hickey's template functor callback approach is slightly better than C++11's lambda functions. It's more direct and closer to the ideal solution. It also works with pre-C++11 compilers. I've used this for many years and recommend it.


Note that the above makeFunctor() does not have the extra first parameter to differentiate between the functor types. I'll let Rich explain from his article: "I must come clean at this point, and point out that the syntax above for makeFunctor() is possible only in the proposed language, because it requires template members (specifically, the Functor constructors would have to be templates). In the current language the same result can be achieved by passing to makeFunctor() a dummy parameter of type ptr-to-the-Functor-type-you-want-to-create. This iteration of the callback library requires you pass makeFunctor() the dummy as the first parameter. Simply cast 0 to provide this argument." My current example code has this dummy parameter as I've not yet found a version of this callback header that supports the above syntax. A search should turn up a version of the header that can handle this. I'm pretty sure I've seen them go by. (Template members are covered on pg 672 of Lippman 2013 if you want to give it a shot. Might be worth some fiddling.)


Command decouples the object that invokes the operation from the onethat knows how to perform it. To achieve this separation, the designercreates an abstract base class that maps a receiver (an object) with anaction (a pointer to a member function). The base class contains anexecute() method that simply calls the action on the receiver.


Modern embedded systems with complex microcontroller and processor architectures incorporate more and more software that has to be planned and implemented faster than ever. System functions are increasingly shifted to software. Consequently, software has to be based on architecture and design. Today, you will use efficient software design methods with C-programming. In many cases, requirements need to be met which are subject to standards and safety-critical aspects. Real-time capability, reusability, adjustability to changing basic conditions and enhanced readability are key factors in software.


In this case, BUILD is needed since we have overridden the default new constructor. bless is eventually invoking it with the two named arguments that correspond to the two properties without a default value. With its signature, BUILD converts the two positionals to the two attributes, &!callback and @!dependencies, and returns the object (or turns it in to the next phase, TWEAK, if available).


In the Task class, the first three lines inside the block all declare attributes (called fields or instance storage in other languages). Just as a my variable cannot be accessed from outside its declared scope, attributes are not accessible outside of the class. This encapsulation is one of the key principles of object oriented design.


After all of the dependencies have completed, it's time to perform the current Task's task by invoking the &!callback attribute directly; this is the purpose of the parentheses. Finally, the method sets the $!done attribute to True, so that subsequent invocations of perform on this object (if this Task is a dependency of another Task, for example) will not repeat the task.


Private attributes really are private. This means that bless is not allowed to bind things to &!callback and @!dependencies directly. To do this, we override the BUILD submethod, which is called on the brand new object by bless:


Since BUILD runs in the context of the newly created Task object, it is allowed to manipulate those private attributes. The trick here is that the private attributes (&!callback and @!dependencies) are being used as the bind targets for BUILD's parameters. Zero-boilerplate initialization! See objects for more information.


While multiple inheritance is a useful concept to know and occasionally use, it is important to understand that there are more useful OOP concepts. When reaching for multiple inheritance it is good practice to consider whether the design wouldn't be better realized by using roles, which are generally safer because they force the class author to explicitly resolve conflicting method names. For more information on roles, see Roles.


This is the callback for the ROS service which is in charge of resetting the counter. Here we check if the boolean inside the SetBool service request is true. If yes, then we simply set the global counter to zero, and return a success flag and message.


The main function of the program. First we initialize the node with the init_node() function. We can then create the publisher, the subscriber, and the service server. Note that the publisher variable was declared previously in the global scope so we can use it on the callback_number() function.


This section describes some of the available design alternatives: adding callbacks to oneway messages, relying on Thread.join, building utilities based on Futures, and creating worker threads. Section 4.4 revisits and extends these techniques in the context of improving the performance of computationally intensive tasks on parallel processors.


From the perspective of pure oneway message passing, the most natural way to deal with completion is for a client to activate a task via a oneway message to a server, and for the server later to indicate completion by sending a oneway callback message to the caller. This efficient, asynchronous, notification-based style applies best in loosely-coupled designs in which completion of the service triggers some independent action in the client. Completion callback designs are sometimes structurally identical to Observer designs (see 3.5.2).


To set up such a FileReader, or any other service using completion callbacks, you must first define a client interface for callback messages. The methods in this interface are substitutes of sorts for the kinds of return types and exceptions that would be associated with procedural versions of the service. This usually requires two kinds of methods, one associated with normal completion, and one associated with failure that is invoked upon any exception. 2ff7e9595c


0 views0 comments

Recent Posts

See All

Comments


bottom of page