Beefy Boxes and Bandwidth Generously Provided by pair Networks
Come for the quick hacks, stay for the epiphanies.
 
PerlMonks  

Re: OT: Design question

by tmoertel (Chaplain)
on Sep 30, 2004 at 21:27 UTC ( [id://395487]=note: print w/replies, xml ) Need Help??


in reply to OT: Design question

The first task is to understand the problem clearly. Let's see what we can dig out of your description:

  1. You have widgets of various types.
  2. Each type of widget has a set of properties.
  3. Each type of property has an associated set of events that you want to trigger when a widget having a property of that type is inserted.
  4. There are many types of events.

Most of that is straightforward. The only tricky part is understanding the relationships among property types and the events you want to trigger. Ignoring design and implementation issues for now, let's just create a written specification to capture these relationships. For example, we might have the following:

PropertyA triggers Event1(a), Event3(b) PropertyB # triggers no events PropertyC triggers Event3(c) ...
The first line says, "Properties of type A trigger an event of type 1 having parameters a and also trigger an event of type 3 having parameters b." Alternatively, we could specify these relationships in a table, which might make things clearer, and/or easier to maintain:
Property Event 1 2 3 4 ... ======== =========== A a b B C c ...

Because these relationships are the crux of your maintenance concerns, we ought to make them easy for humans to specify and change. I would therefore not represent them as code. Rather, I would try to use a representation as close to the specifications above as possible, optimizing for humans instead of the computer. For example, we could use a configuration file similar to our first specification or a database table modeled on the second.

In either case, our implementation would read the specifications upon initialization and then wire up the relationships accordingly via code generation or other means. The idea is to make the computer do the work, not humans.

For example, our wiring might be as simple as converting the specifications into a lookup table that maps property names (class name) to event handlers:

my $property_type_events = { Property::A => [ handler { Event::E1->new(@_) } 'a' , handler { Event::E3->new(@_) } 'b' ] Property::B => [ ], Property::C => [ handler { Event::E3->new(@_) } 'c' ], ... };
Here, handler is a helper function that takes a block of code that builds an event object of a particular type using the given run-time parameters (represented by placeholders like 'a' and 'b') and returns a function that when called creates and triggers the event:
sub handler(&@) { my ($event_ctor, @args) = @_; return sub { my $event = $event_ctor->(@args); return $event->trigger(@_); }; }

Now, just make sure that all of your Widget objects have a get_properties method that returns a list of the properties they have, and it's easy to handle the event triggering for an arbitrary widget:

sub handle_widget_insertion_events($) { my ($widget) = @_; for my $property ($widget->get_properties) { my $handlers = $property_type_events{ ref($property) }; next unless $handlers; for my $handler (@$handlers) { my $rc = $handler->( "pass trigger args here" ); # do something with result code $rc } } }
That's the approach that I would take: Make the tricky relationships easy to maintain by tailoring their representation for the purpose. The implementation can then be built automatically from that representation.

I would repeat the approach for the other parts of the problem where it makes sense. For example, if the relationships among widget types and their properties are complicated, I would create an external specification for them, too, and build code from that.

Hope this helps.

Cheers,
Tom

Replies are listed 'Best First'.
Re^2: OT: Design question
by dragonchild (Archbishop) on Sep 30, 2004 at 22:30 UTC
    It's actually even more complicated than that. There are, say, 5 different widget properties. (It's actually more like 20, and some aren't actually widget properties, but are based on widget properties.) Every widget will have a value for every property. The handlers trigger based on the values for the widget properties, not if the widget has a given property. So, I really need to have a way to ask a given handler if it needs to fire based on a given widget and the values for all the properties. And, the issue is how to maintain in a human-readable fashion all these mappings.

    I do appreciate the fact that you've helped refine the problem for me. It's not about the architecture of triggering events and event handlers. It's really about how to make this system accessible by humans. I need to abstract out the complexity and put it in one place. The complexity is going to be there and I can't wish it away. What I need to do is concentrate it so that it's not spread throughout the code.

    Being right, does not endow the right to be rude; politeness costs nothing.
    Being unknowing, is not the same as being stupid.
    Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
    Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

      It's not about the architecture of triggering events and event handlers. It's really about how to make this system accessible by humans.

      Amen.

      Since you need build the app to support ongoing adaptability / expandability, the first thing to do is think about how to optimize, from the perspective of the people who will be doing this, the task of adding / modifying app behaviors.

      That means working out a conceptual model for controlling the app that is easy to explain and understand, and then working out a set of cookbook steps that involve the minimum possible amount of input from the person doing the task -- i.e. if the task is structured so that any single piece of information or logic needs to be supplied in more than once place by a human, you probably haven't got the right design yet.

      That said, I like BrowserUK's take on the matter, which seems to go in the same direction as tmoertel's notion: capture the relations as data rather than as blocks / objects / classes of code. That will generally mean less code, and less work for the humans who handle the "behavior modifications"; those are both good things.

      It's actually even more complicated than that.
      Still, I wouldn't change the approach: Create an external specification that is tailored to capture your application's complicated relationships in a way that is intuitive to humans; then, translate the specification into the desired code, either at run time or during a pre-processing step.
      There are (about 20) different widget properties..., and some aren't actually widget properties, but are based on widget properties.) Every widget will have a value for every property.
      This is important information that will help us design our specification.
      The handlers trigger based on the values for the widget properties, ....
      This, too, is good stuff to know. (Maybe you could tell us what you're really doing?)
      So, I really need to have a way to ask a given handler if it needs to fire based on a given widget and the values for all the properties.
      Not necessarily. You might, for example, be better served by having an object of specialized type whose responsibility it is to manage widget property–to–handler relationships. Then you would ask it what handlers ought to be fired, not the handlers themselves. But that's a decision to be made later. For now, let's just figure out how to specify the relationships.

      If I'm reading your new constraints correctly, the relationship we're concerned with is that between widget properties and handlers, not widgets and handlers. So, let's start out with a simple matrix that enumerates all of the possible points of intersection:

      Property Handler 1 2 3 4 ... ======== =========== A a b B C c ...
      This looks much like what I suggested before, but now we have a new interpretation. Each a or b in the matrix represents a set of conditions that govern whether the associated handler (given by column) may fire for the current widget based on its current value of the associated property (given by row). In other words, we can interpret the column under Handler 3 as saying, Handler 3 will fire for the current widget if the current widget's A property satisfies condition b and its C property satisfies condition c. Our rule, for now, is that all associated conditions must be satisfied in order to fire the handler.

      If that's all the more complicated your relationships are, then we're done. But maybe the all-or-none rule is too limiting. Maybe Handler 3 ought to fire if b(A) or if c(C) is satisfied. Then, a simple matrix will not suffice; we'll need a way of specifying expressions:

      Handler Firing condition ======= ================ 1 a(A) 2 ... 3 b(A) || c(C) ...
      If we filled out this two-column table for all of your handlers, we would have a lot of redundancy because, in your original question, you said that, "there's only really about 4-5 different types of events, just with different parameters." So, let's allow the handlers and conditions to be parameterized (and we'll give the handlers names, while we're at it):
      Handler Firing condition ======= =================== Page("Jim") a(A) Email("foo-alert@...") b(A) || c(C, "foo") Email("bar-alert@...") b(A) || c(C, "bar")
      At this point, we have to be careful that our specification doesn't devolve into a bunch of if-statement equivalents. But I'm going to stop here because I'm just guessing about what you really need and don't know which refinement to make next. I don't have enough information to know whether it would be better, for example, to factor out redundancy in our specification or to leave it in. This is where knowing about what you're really doing would be helpful.

      Depending on your needs, reasonable refinements might include factoring out common subexpressions, adding environmental context, or adding a related specification table. Each involves trading one kind of complexity for another, and whether it makes sense for you depends on the costs of managing different kinds of complexity in your application and programming culture. Only you know enough to make these decisions at this point.

      Cheers,
      Tom

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://395487]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others browsing the Monastery: (2)
As of 2024-04-25 20:39 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found