|
From: jimbalo22 on 12 Jan 2008 16:20 Hi, I have a collection of business objects that the user works on via data binding. The user needs to be able to work with a subset of the collection and I am wondering what the best way to accomplish this is. Example: InvoiceCollection->Invoice->PaymentCollection->Payment The user wants to only work with invoices or payments for a certain date. Two ways I can think of are 1) work with a subset, or 2) Work with the full set in a filtered fashion. I am leaning toward 2), but it gets quite messy with a bunch of filter related methods in the public interfaces. Any help on this would be appreciated. Thanks, Jim
From: H. S. Lahman on 13 Jan 2008 12:27 Responding to Jimbalo22... > Hi, > > I have a collection of business objects that the user works on via > data binding. The user needs to be able to work with a subset of the > collection and I am wondering what the best way to accomplish this is. > > Example: > > InvoiceCollection->Invoice->PaymentCollection->Payment I assume this notation means something like: [Customer] | 1 | | R1 | | is billed with | * [Invoice] + date | 1 | | R2 | | are paid by | * [Payments] + date where InvoiceCollection and PaymnentCollection implement the R1 and R2 1:* relationships at OOP time. > > The user wants to only work with invoices or payments for a certain > date. > > Two ways I can think of are 1) work with a subset, or 2) Work with the > full set in a filtered fashion. I am leaning toward 2), but it gets > quite messy with a bunch of filter related methods in the public > interfaces. The answer definitely depends -- on what problems are being solved and at what level of abstraction. The most general approach is (2) and most of the abstract action languages (AALs) used in OOA/D will have a WHERE clause available for any relationship navigation to filter the set of objects accessed. Thus in an AAL method one might have something like invoiceSet = this -> R1 WHERE (date=20080113) FOREACH invoice IN invoiceSet // process invoice objects when the user wants invoices, or paymentSet = this -> R1 -> R2 WHERE (date=20080113) FOREACH payment IN paymentSet // process payment objects when the user wants payments. [Note that the WHERE clause filters the date attribute in the target set. When a multistage path is navigated it is assumed that the membership of the intermediate collections already limits the set adequately. If not, one needs to do the second fragment in two stages.] This is fine so long as the filter is simple, such as a date. Each collection is optimized for efficient access via a date. As you point out, it gets somewhat messy if there are multiple filters (e.g., one also may want payments where the amount was less than some percentage of the outstanding balance). Then the collection class needs an interface for each distinct selection criteria. Each accessor may need a specialized implementation as well. However, that is pretty much why collection classes exist; they manage collections. More to the point, that is /all/ they do; they encapsulate the collection management in one place. IOW, the collection class isolates and encapsulates complexity of a highly focused nature. In general that is a Good Thing. So there is nothing inherently wrong with the second approach. Note that in the AAL, one just needs to substitute "amount<100" in the WHERE clause to deal with another selection criteria in the second fragment above. The details of how one mucks about to extract that set is left to the low level implementation so during OOA/D the application developer doesn't need to think about it. However, at OOP time one addresses other issues like performance and maintainability. Suppose Invoices and Payments are very rarely added compared to how often the various selection criteria are accessed. In that case it may well be more efficient to have multiple 1:* relationships in parallel, each with its own optimized collection mechanics. Thus one has R2A optimized to access by <arbitrary> date and R2B optimized to access by some amount criteria. For example, one collection is sorted by date while the other is sorted by amount. The overhead of adding the same entry to each collection is <hopefully> small compared to accessing the same elements many times. Maintainability may also drive the use of (1) simply because the internal mechanisms for optimizing around several disparate criteria are just making the collection object too complex to modify. This is a variation on your concern about the interface being "messy". However, multiple interface methods are usually not the core problem; the need to break up the collection is usually driven by the complexity of the implementation. Bottom line: in OOA/D one wants to think generically about (2) without worrying about the details and the OOA/D notations are designed around that. But at OOP time one is addressing other issues and those may drive using (1). -- There is nothing wrong with me that could not be cured by a capful of Drano. H. S. Lahman hsl(a)pathfindermda.com Pathfinder Solutions http://www.pathfindermda.com blog: http://pathfinderpeople.blogs.com/hslahman "Model-Based Translation: The Next Step in Agile Development". Email info(a)pathfindermda.com for your copy. Pathfinder is hiring: http://www.pathfindermda.com/about_us/careers_pos3.php. (888)OOA-PATH
From: jimbalo22 on 13 Jan 2008 15:04 Lahman, Thanks for the reply. > > Example: > > > InvoiceCollection->Invoice->PaymentCollection->Payment > > I assume this notation means something like: > > [Customer] > | 1 > | > | R1 > | > | is billed with > | * > [Invoice] > + date > | 1 > | > | R2 > | > | are paid by > | * > [Payments] > + date Almost. The application is for scheduling of payments of invoices and what I posted was a simplfied version of this. Here is a more accurate diagram: [VendorCollection] | 1 | | | * [Vendor] | 1 | | | 1 [InvoiceCollection] | 1 | | | * [Invoice] | 1 | | | 1 [PaymentCollection] | 1 | | | * [Payment] > The answer definitely depends -- on what problems are being solved and > at what level of abstraction. Here is a brief description of the app. It is a WinForm (C#) app with: * a Vendor grid bound to [VendorCollection], showing each [Vendor] with amount due, payment total, etc. * an Invoice grid bound to [InvoiceCollection], showing each [Invoice] for the selected vendor * a Payment grid bound to [PaymentCollection], showing each [Payment] scheduled for the selected invoice. The user needs to be able to view and work with a variety of filtered subsets of the vendors / invoices: 1) The Vendor grid shows daily totals, monthly totals and grand totals for each vendor. 2) The user can select one or more vendors in the vendor grid and perform operations on all payments assigned to the invoices for those vendors for the day (for example: remove payments, change payment date, add payments). 3) The user can select one or more vendors in the vendor grid and perform operations on all payments assigned to the invoices for those vendors for the month. 4) The user can select one or more vendors in the vendor grid and perform operations on all payments assigned to the invoices for those vendors (no time-limiter) 5) The user can select one or more invoices in the invoice grid and perform operations on all payments assigned to those invoices. I have a Setfilter(DateTime filterDate) methods defined in the classes, so that when you call SetFilter on for example [VendorCollection], it in turns call SetFilter on each [Vendor] and so forth. > The most general approach is (2) and most of the abstract action > languages (AALs) used in OOA/D will have a WHERE clause available for > any relationship navigation to filter the set of objects accessed. Thus > in an AAL method one might have something like > > invoiceSet = this -> R1 WHERE (date=20080113) > FOREACH invoice IN invoiceSet > // process invoice objects > > when the user wants invoices, or > > paymentSet = this -> R1 -> R2 WHERE (date=20080113) > FOREACH payment IN paymentSet > // process payment objects I am not familiar with Abstract Action Languages, but here is an example of a method in [InvoiceCollection]: public double PaymentTotalForDay { get { double amount = 0; foreach (Vendor vendor in this) amount += vendor.PaymentTotalForDay; return amount; } } This will return the total payment amount for the date specified in an earlier call to Setfilter(filterDate). But since the VendorGrid also needs to show PaymentTotalForMonth (regardless of day filtered on) and PaymentTotal (no time-limiter), I wind up with two similar methods for this. And then there are several other filter-specific methods, so it does get a bit messy. > when the user wants payments. [Note that the WHERE clause filters the > date attribute in the target set. When a multistage path is navigated it > is assumed that the membership of the intermediate collections already > limits the set adequately. If not, one needs to do the second fragment > in two stages.] I belive I am doing something equivalent using the SetFilter method above. > However, that is pretty much why collection classes exist; they manage > collections. More to the point, that is /all/ they do; they encapsulate > the collection management in one place. IOW, the collection class > isolates and encapsulates complexity of a highly focused nature. In > general that is a Good Thing. So there is nothing inherently wrong with > the second approach. I know, but I guess I was looking for a way to separate out all the filter-specific responsibilities in such a way as to simplify and make the design cleaner, but it seems that doing that just adds more complexity. > Note that in the AAL, one just needs to substitute "amount<100" in the > WHERE clause to deal with another selection criteria in the second > fragment above. The details of how one mucks about to extract that set > is left to the low level implementation so during OOA/D the application > developer doesn't need to think about it. Since I am not familiar with AAL I do not quite follow this - is this something I could use in my C# app as described above? > However, at OOP time one addresses other issues like performance and > maintainability. Suppose Invoices and Payments are very rarely added Actually, Invoices are not added / removed, just paid. Payments are what changes (=are modified, added & removed). Thanks, Jim Ps. It is probably not relevant, but in the app I am using Rockford Lhotka's CSLA framework.
From: H. S. Lahman on 14 Jan 2008 19:17 Responding to Jimbalo22... >>> InvoiceCollection->Invoice->PaymentCollection->Payment >> I assume this notation means something like: >> >> [Customer] >> | 1 >> | >> | R1 >> | >> | is billed with >> | * >> [Invoice] >> + date >> | 1 >> | >> | R2 >> | >> | are paid by >> | * >> [Payments] >> + date > > Almost. The application is for scheduling of payments of invoices and > what I posted was a simplfied version of this. Here is a more > accurate diagram: > [VendorCollection] > | 1 > | > | > | * > [Vendor] > | 1 > | > | > | 1 > [InvoiceCollection] > | 1 > | > | > | * > [Invoice] > | 1 > | > | > | 1 > [PaymentCollection] > | 1 > | > | > | * > [Payment] > >> The answer definitely depends -- on what problems are being solved and >> at what level of abstraction. > > Here is a brief description of the app. It is a WinForm (C#) app > with: > * a Vendor grid bound to [VendorCollection], showing each [Vendor] > with amount due, payment total, etc. > * an Invoice grid bound to [InvoiceCollection], showing each [Invoice] > for the selected vendor > * a Payment grid bound to [PaymentCollection], showing each [Payment] > scheduled for the selected invoice. We have a disconnect. Your model is typical of how one uses CRUD/USER layered model infrastructures. That sort of processing is not a good target for OO techniques. The RAD IDEs have already abstracted the invariants of the processing in terms of Form/Query/Table and have provided a lot of reusable infrastructure that one would have to develop from scratch using OO techniques. In addition, such infrastructures are, at best, object-based rather than object-oriented. One way the difference is manifested is in separation of concerns for different problem spaces. In an OOA/D context the model I provided would be relevant in a subsystem that modeled the customer's business problem. The rendering of the display would be handled in a different subsystem that modeled the particular display paradigm. So for a GUI one might have a model like: [Display] | 1 | | R1 | | contains | * [Window] | 1 | | R2 | | Control | * [Control] where [Control] is subclassed into things like [ListBox] and [Grid]. In the GUI subsystem the only business semantics exists as labels and titles for particular instances that are provided by the business subsystem or, more commonly, through a configuration file. Your [InvoiceCollection] and [PaymentCollection] entities organize data for the display and would map into instances of [Window] and [Control]. (In fact, they would both likely be GridControl instances.) As it happens, in CRUD/USER processing the use of the Form paradigm for I/O conveniently allows display artifacts to map 1:1 to RDB tables and Queries. That allows one to raise the level of abstraction and hide all the Window/Control manipulations under the hood in the infrastructure. So one doesn't need to think explicitly about windows and controls and one can focus on manipulating Forms like InvoiceCollection and PaymentCollection. The developer still needs to provide a mapping, though, between those Forms and RDB tables and queries. Typically that is done in the business layer where one mixes classes like [Invoice] and [InvoiceCollection] and uses relationships to enforce the inherent referential integrity rules. That works fine for CRUD/USER processing because all the mapping is essentially 1:1. No harm; no foul. But mixing paradigms is usually a very bad idea from an OOA/D perspective once one is outside the realm of CRUD/USER processing. That's because the data structures needed to optimize the customer's problem solution are usually not the same as those needed to optimize data storage or display and the 1:1 mapping breaks down. So in OOA/D one would be very careful to put [Invoice] and [Payment] in one subsystem and [InvoiceCollection] and [PaymentCollection] in another subsystem. That manages the mapping complexity by separating the concerns of different problem spaces (display vs. business) so that one can focus on one paradigm at a time. One would also encapsulate the subsystems strongly so that objects in one subsystem would have no idea that the objects in the other subsystem even exist. That, in turn, simplifies the mapping problem to data identity so that each subsystem can map message data packets into its own special view. I point all this out because it reflects a major disconnect between OOA/D and the RAD infrastructures. So far your application sounds like classic CRUD/USER processing. In that case using the RAD paradigm and infrastructures will probably save you a ton of keystrokes. But that will necessarily result in different models than I would use as an OOA/D guy when solving non-CRUD/USER problems. (When I need to solve a CRUD/USER problem I forget about OO and use a RAD IDE.) Thus I can't really express the points I was trying to make in terms of your model. >> The most general approach is (2) and most of the abstract action >> languages (AALs) used in OOA/D will have a WHERE clause available for >> any relationship navigation to filter the set of objects accessed. Thus >> in an AAL method one might have something like >> >> invoiceSet = this -> R1 WHERE (date=20080113) >> FOREACH invoice IN invoiceSet >> // process invoice objects >> >> when the user wants invoices, or >> >> paymentSet = this -> R1 -> R2 WHERE (date=20080113) >> FOREACH payment IN paymentSet >> // process payment objects > > I am not familiar with Abstract Action Languages, but here is an > example of a method in [InvoiceCollection]: The only point I was trying to make is that in OOA/D the AAL syntax essentially restricts one to your type (2) approach because that is the most general. > > public double PaymentTotalForDay > { > get > { > double amount = 0; > foreach (Vendor vendor in this) > amount += vendor.PaymentTotalForDay; > > return amount; > } > } > > This will return the total payment amount for the date specified in an > earlier call to Setfilter(filterDate). But since the VendorGrid also > needs to show PaymentTotalForMonth (regardless of day filtered on) and > PaymentTotal (no time-limiter), I wind up with two similar methods for > this. And then there are several other filter-specific methods, so it > does get a bit messy. I am confused. In your model each InvoiceCollection has only one Vendor related to it. Where does the foreach collection come from? Assuming one needs a total paid by a vendor for a particular date across all vendor invoices, I don't see a need for InvoiceCollection, PaymentCollection, and SetFilter. If we had Vendor::getTotalPaymentsByDate(date), all that method needs to do is "walk" the R1 and R2 relationships in my model. For each Payment accessed one then just checks the date and, if a match, accumulates the payment amount. If one needs a total for all vendors, we just need to iterate over the vendors and sum the results of calling Vendor::getTotalPaymentsByDate. >> However, that is pretty much why collection classes exist; they manage >> collections. More to the point, that is /all/ they do; they encapsulate >> the collection management in one place. IOW, the collection class >> isolates and encapsulates complexity of a highly focused nature. In >> general that is a Good Thing. So there is nothing inherently wrong with >> the second approach. > I know, but I guess I was looking for a way to separate out all the > filter-specific responsibilities in such a way as to simplify and make > the design cleaner, but it seems that doing that just adds more > complexity. I am not sure where the filters come in. But I am probably looking at the solution quite differently. > >> Note that in the AAL, one just needs to substitute "amount<100" in the >> WHERE clause to deal with another selection criteria in the second >> fragment above. The details of how one mucks about to extract that set >> is left to the low level implementation so during OOA/D the application >> developer doesn't need to think about it. > Since I am not familiar with AAL I do not quite follow this - is this > something I could use in my C# app as described above? I think one way to express my point is that the filter is the WHERE clause and one specifies the attribute(s) to be checked in it. Identify a different attribute withing the same syntax and one has a different filter. But, as I noted, that filter is applied to the set of target objects (e.g., Payments) regardless of how long the relationship path is to obtain them. One way to think of it is that there are three pieces of the problem. One is to get a set of objects that are of interest. This is just a matter of relationship navigation, which is orthogonal to class semantics. That is, wherever one needs to construct, say, a Payment total, one needs to find a relationship path to the relevant Payment objects and navigate it. That is just a matter of "walking" the collection classes that implement the relationship path segments. The second piece of the problem if to extract a subset of the navigable objects based on problem constraints (if necessary). This is the "filtering". One tests attributes of the collection resulting from relationship navigation and prunes those that are not relevant. The third piece is to actually do something with the objects, like accumulating a total, once one has the relevant subset. My point here is that the "filtering" is just part of the relationship navigation. That is, the attribute testing is done as one accesses each object. That requires no special objects. (If the testing is complex, it may be delegated to another object, but it will always be invoked synchronously by whoever is doing the relationship navigation.) -- There is nothing wrong with me that could not be cured by a capful of Drano. H. S. Lahman hsl(a)pathfindermda.com Pathfinder Solutions http://www.pathfindermda.com blog: http://pathfinderpeople.blogs.com/hslahman "Model-Based Translation: The Next Step in Agile Development". Email info(a)pathfindermda.com for your copy. Pathfinder is hiring: http://www.pathfindermda.com/about_us/careers_pos3.php. (888)OOA-PATH
From: jimbalo22 on 15 Jan 2008 01:19
Lahman, First of all, thanks for taking the time to explain all of this. > We have a disconnect. Your model is typical of how one uses CRUD/USER > layered model infrastructures. That sort of processing is not a good > target for OO techniques. The RAD IDEs have already abstracted the > invariants of the processing in terms of Form/Query/Table and have > provided a lot of reusable infrastructure that one would have to develop > from scratch using OO techniques. In addition, such infrastructures are, > at best, object-based rather than object-oriented. Interesting. If OO is the wrong tool for this job, it would explain why I am having a hard time coming up with a smooth design for this (that and the fact that I still have much to learn in the OOA/D arena). > One way the difference is manifested is in separation of concerns for > different problem spaces. In an OOA/D context the model I provided would > be relevant in a subsystem that modeled the customer's business problem. > The rendering of the display would be handled in a different subsystem > that modeled the particular display paradigm. So for a GUI one might > have a model like: > > [Display] > | 1 > | > | R1 > | > | contains > | * > [Window] > | 1 > | > | R2 > | > | Control > | * > [Control] > > where [Control] is subclassed into things like [ListBox] and [Grid]. In > the GUI subsystem the only business semantics exists as labels and > titles for particular instances that are provided by the business > subsystem or, more commonly, through a configuration file. When you say "subsystem" do you mean layers? The app is 3-tiered. > Your [InvoiceCollection] and [PaymentCollection] entities organize data > for the display and would map into instances of [Window] and [Control]. > (In fact, they would both likely be GridControl instances.) Yes they do organize data for display, but also for operation - the user can select one or many vendors or invoices or payments and perform some action on these (like move payment dates or remove payments, etc.). > But mixing paradigms is usually a very bad idea from an OOA/D > perspective once one is outside the realm of CRUD/USER processing. > That's because the data structures needed to optimize the customer's > problem solution are usually not the same as those needed to optimize > data storage or display and the 1:1 mapping breaks down. So in OOA/D one > would be very careful to put [Invoice] and [Payment] in one subsystem > and [InvoiceCollection] and [PaymentCollection] in another subsystem. > That manages the mapping complexity by separating the concerns of > different problem spaces (display vs. business) so that one can focus on > one paradigm at a time. One would also encapsulate the subsystems > strongly so that objects in one subsystem would have no idea that the > objects in the other subsystem even exist. Sorry, but I am not sure if I follow the last part: If [InvoiceCollection] contains [Invoices], how can it be unware of [Invoice]? Also, would you mind elaborating on how you would separate the collections from the elements they contain into different subsystems? Do you know if C# (VS 2005) lends it self well to this concept? > I point all this out because it reflects a major disconnect between > OOA/D and the RAD infrastructures. So far your application sounds like > classic CRUD/USER processing. In that case using the RAD paradigm and > infrastructures will probably save you a ton of keystrokes. But that > will necessarily result in different models than I would use as an OOA/D > guy when solving non-CRUD/USER problems. (When I need to solve a > CRUD/USER problem I forget about OO and use a RAD IDE.) Thus I can't > really express the points I was trying to make in terms of your model. That makes sense. I actually had a similar thought earlier: to simply use a Dataset with DataViews for filtering. I have not used a DataSet in a long time, but I guess I would have to pass the dataset / dataview into business layer methods for the execution of operations on the data (such as the InvoiceCollection.ChangeAllPayDates(DateTime newDate) method I currently have). What are your thoughts on this? By the way, what is your RAD IDE of choice? > > public double PaymentTotalForDay > > { > > get > > { > > double amount = 0; > > foreach (Vendor vendor in this) > > amount += vendor.PaymentTotalForDay; > > > return amount; > > } > > } > > > This will return the total payment amount for the date specified in an > > earlier call to Setfilter(filterDate). But since the VendorGrid also > > needs to show PaymentTotalForMonth (regardless of day filtered on) and > > PaymentTotal (no time-limiter), I wind up with two similar methods for > > this. And then there are several other filter-specific methods, so it > > does get a bit messy. > > I am confused. In your model each InvoiceCollection has only one Vendor > related to it. Where does the foreach collection come from? The above snipped is from [VendorCollection] and it iterates though each [Vendor]. > Assuming one needs a total paid by a vendor for a particular date across > all vendor invoices, I don't see a need for InvoiceCollection, > PaymentCollection, and SetFilter. If we had > Vendor::getTotalPaymentsByDate(date), all that method needs to do is > "walk" the R1 and R2 relationships in my model. For each Payment > accessed one then just checks the date and, if a match, accumulates the > payment amount. If one needs a total for all vendors, we just need to > iterate over the vendors and sum the results of calling > Vendor::getTotalPaymentsByDate. AFAIK, there is no way to use an argument with databinding. Example: one of the gridcolumns is bound to VendorCollection.PaymentTotalForDay; I don't think you can bind it to VendorCollection.getTotalPaymentsByDate(date). But I might be wrong - if you (or anyone) knows how to do this, please let me know. > > I know, but I guess I was looking for a way to separate out all the > > filter-specific responsibilities in such a way as to simplify and make > > the design cleaner, but it seems that doing that just adds more > > complexity. > > I am not sure where the filters come in. But I am probably looking at > the solution quite differently. By "filter-specific responsiblities" I mean methods for retrieving and working with a subset of the collections (like the SetFilter method and the PaymentTotalForMonth). > One way to think of it is that there are three pieces of the problem. > One is to get a set of objects that are of interest. This is just a > matter of relationship navigation, which is orthogonal to class > semantics. That is, wherever one needs to construct, say, a Payment > total, one needs to find a relationship path to the relevant Payment > objects and navigate it. That is just a matter of "walking" the > collection classes that implement the relationship path segments. > > The second piece of the problem if to extract a subset of the navigable > objects based on problem constraints (if necessary). This is the > "filtering". One tests attributes of the collection resulting from > relationship navigation and prunes those that are not relevant. > > The third piece is to actually do something with the objects, like > accumulating a total, once one has the relevant subset. > > My point here is that the "filtering" is just part of the relationship > navigation. That is, the attribute testing is done as one accesses each > object. That requires no special objects. (If the testing is complex, it > may be delegated to another object, but it will always be invoked > synchronously by whoever is doing the ... Yes, this is basically what I doing now. One problem I am running into is that the performance is not that great due to the many traversals of the various collections. I will profile and see what can be done (caching is tricky in this scenario). If I cannot resolve the performance issue, I will probably look into using a DataSet & DataView instead since they might be better tools for this job. Thanks, Jim |