From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00,FREEMAIL_FROM autolearn=ham autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: 103376,19d0849c68914783,start X-Google-Attributes: gid103376,public From: Thom Brooke Subject: Design problem using Multiple Dispatch or Redispatch (long) Date: 2000/03/14 Message-ID: <38CDAA56.36B9E1C1@yahoo.com> X-Deja-AN: 597173975 Content-Transfer-Encoding: 7bit X-Accept-Language: en Content-Type: text/plain; charset=us-ascii X-Complaints-To: abuse@earthlink.net X-Trace: newsread1.prod.itd.earthlink.net 953002797 158.252.163.251 (Mon, 13 Mar 2000 18:59:57 PST) Organization: EarthLink Inc. -- http://www.EarthLink.net MIME-Version: 1.0 NNTP-Posting-Date: Mon, 13 Mar 2000 18:59:57 PST Newsgroups: comp.lang.ada Date: 2000-03-14T00:00:00+00:00 List-Id: I've been struggling with a design problem for some time now, and I'm looking for suggestions (or some reason why it won't work). I have developed a solution, but it's ugly -- little better than Ada83. I want a nice, clean, Ada95 solution that does it _right_. Here's the situation: The system consists of a bunch of "commands", and several kinds of "processor" which "execute" the commands. The commands are related in that commands can be received in any order, and they have some common structure, but each specific command is distinct -- typically in terms of the fields defined within the command. The processors, too, are related, in that they represent alternative implementations of the same interface. Each processor supports a subset of the commands, and these subsets may overlap (i.e., two processors may support the same command). The way that each processor executes a particular command may be completely different. In general, command execution requires detailed knowledge of the internal representation of the processor, as well as the structure and fields of the command itself. Furthermore, there may be multiple instances of each "kind" of processor. The set of commands may change over time (typically, new commands will be added. But it is possible that existing commands will be modified or deleted). Also, the various processors may be "upgraded" to support additional commands. What I'm trying to do is define the commands, the processors, and the mechanism for executing commands on the processors so as to reduce redundant and error-prone coding, and make it easier to add new commands, new processors, or new processor command execution support. So far, I've been trying to devise some kind of multiple-dispatching or redispatching architecture -- I've also tried to work in mixins -- to avoid parallel "if" or "case" statements, but I haven't gotten it to work. I suspect I'm either on entirely the wrong track, or I've missed something really obvious. Here are some of the things I've tried: It seems "obvious" to me that the commands represent a family of types. There is a Root_Command type, with the general command operations (the commands are received as a stream of bytes which must be parsed and formatted into their internal representation). Each specific command type is derived from the Root_Command type. At this point, the family is 1-deep. That is, all specific commands are derived directly from Root_Command; no commands are derived from other commands. However, that may change (and it greatly complicates maintenance for if/case statememt based approaches). So I have something like this: with Supporting_Stuff; package Commands is type Root_Command is abstract tagged private; function Parse_Command (Input : in Bytes) return Root_Command is abstract; -- other general Command subprograms. Not necessarily abstract private type Root_Command is ... end Commands; with Commands; package Specific_Commands is type Command_1 is new Root_Command with ... function Parse_Command ... type Command_2 is new Root_Command with ... function Parse_Command ... end Specific_Commands. Similarly, since the Processors are "alternative implementations", an abstract root class with specific derived types for the various implementations seemed obvious. Each instance of a processor has some "fetch" function available: function Get_Next_Command (P : Processor) return Root_Command'Class; and some way to Execute specific commands. The objective was to be able to write something like the following for each processor: loop declare Cmd : Root_Command'Class := Get_Next_Command(Myself); begin Execute (Myself, Cmd); end; end loop; where the Execute() procedure would dispatch on the specific Command type, to some procedure written for that command and the current processor. With C different specific commands, and P different processor variations, there could be up to (C * P) different Execute procedures. That's fine. I want to be able to just write a new Execute procedure, and have it "automatically" picked up (e.g., by dispatching). In particular, what I want to avoid is something like the following: loop declare Cmd : Root_Command'Class := Get_Next_Command(Myself); begin if Cmd in Command_1'Class then Execute_Cmd_1 (Myself, Command_1(Cmd)); elsif Cmd in Command_2'Class then Execute_Cmd_2 (Myself, Command_2(Cmd)); ... end if; end; end loop; Initially, I made Execute a primitive operation of Root_Command. However, that required that Commands know about Processors, which seemed backwards. Furthermore, it couldn't support multiple processor variations: -- In Commands: procedure Execute (P : in out Root_Processors'Class; Cmd : in Root_Comand) is begin P_Execute( P, Root_Command'Class(Cmd)); -- where P_Execute is a primitive operation of -- Root_Processor, and so is dispatching on P. end Execute; Even if Execute turned around and redispatched on the Processor parameter to P_Execute, P_Execute still has to somehow figure out which specific command is coming in. I can't have two controlling operands in Execute, so one has to be class-wide. I looked at making Execute a primitive operation of Processors, but that has the inverse problem: I can dispatch on specific processors (which isn't really necessary, anyway, since execution stays within the same command/processor pipeline), but I can't then dispatch on the Commands. I thought about mixin's, but I'm really not dealing with independent types. Commands and Processors are related; I'm not trying to add an orthogonal facet to, say, an Address_Book class. Now, I can get dispatching on Commands within the different Processor variations by using creating second-level Commands derived in parallel for each supported specific command: -- In Commands: procedure Execute (P : in out Root_Processor'Class; Cmd : in Root_Commands); -- has "null" body. with Specific_Commands; package Processor_1_Commands is type P1_Command_1 is new Specific_Commands.Command_1 with null record; procedure Execute (P : in out Root_Processor'Class; Cmd : in P1_Command_1); -- keep inherited Command operations, like Parse_Command). -- etc. end Processor_1_Commands; And in the body of Processor_1_Commands, implement Execute to require and operate on a Processor_1 type. Similarly, for Processor_2. Unfortunately, this means that the Get_Next_Command function has to return one of the P1_Command_x commands when called from Processor_1, and return a P2_Command_x when called from Processor_2. Only, the command structures are identical, so the only difference would be which specific Processor-Command type the factory called. Parallel code, again. So my dilemma: I _can_ solve the problem using Ada83-esque if/case statements. But this seems like such a "mormal" thing to want to do. It can't be that obscure; I can't believe there isn't some feature or combination of features in Ada95 that will let me do this cleanly. Does anyone have any ideas? -- -- Thom Brooke -- Cut out "_CUT_OUT" to get my real email address.