Collections are Primitives
Once again, Twitter is the driver for a new blog post!
Note: This is just a brain dump. No IDE opened, no re-read. Idea, typing, your eyes suffering.
How do you do Collections in MicroObjects?
I'm not sure how much I've covered it elsewhere; but no posts are popping up... So here we go.
Collections are primitives. Just like strings, ints, floats. I would say bools... but nothing is like bools. Those jerks.
Collections are Primitives. We don't pass them around. We don't interact with them directly. When there is a collection of things that we need to interact with; we wrap it in a class and add behavior to that class.
The simplest example is getting records from the database to display on the screen.
sealed class ItemCollection: IItemCollection{
public ItemCollection(List<Item> items){...}
public ItemsDto DisplayDatabag(){
//turn items into ItemDto and stuff them into the ItemsDto
}
}
There's no real behavior. We have a collection of items and them can get them in a form to display.
We can do whatever we need to accommodate the display, and the object knows how. The databag could (should) feed into an adapter to limit UI widgets to that level. Not doing that was a mistake I've made on a couple projects, it works; but it there is definitely some pollution of the system with UI elements when you don't have the adapter in place.
Advantages of this? Instead of passing the List up?
You can't touch it.
You can't see it.
You can't tell where it came from.
It's invisible! But it is a thing. Not a list of things, just a thing.
We could also have a collection take a DataSet
. No need to "pre-convert"
sealed class DataSetItemCollection: IItemCollection{
public ItemCollection(DataSet ds){...}
public ItemsDto DisplayDatabag(){
//turn items into ItemDto and stuff them into the ItemsDto
}
}
This allows us to create functionality, via an interface, without knowing the shape or source of the data.
What about when we start to have some functionality?
We could add filtering.
sealed class ???ItemCollection: IItemCollection{
public void Exclude(FilterType filterType){...}
public ItemsDto DisplayDatabag(){
//turn items into ItemDto and stuff them into the ItemsDto
}
}
How do we exclude from a list? Iterate and create a new list which continues up. Somewhere in the system, detached from the data, there's a "filter" utility method. Something somewhere will know how to filter the list of items. What if somewhere else needs to filter? Copy/paste? ListFilterHelper
? Crappy options.
When we put the filtering onto our ItemCollection then it's always there. Just there. We can defer the filter work until the data's extracted; and only process the data once.
sealed class ItemCollection: IItemCollection{
public ItemCollection(List<Item> items){...}
public void Exclude(FilterType filterType){...}
public ItemsDto DisplayDatabag(){
foreach(Item item in _items){
if(item.IsType(filterType)) continue;
//Do transform stuff
}
}
}
We could actually not even hit the database until we try to turn it into databags. It's all under our control of when things happen, BECAUSE we put it into an object. The level of control and flexibility that's introduced into the system when we treat collections as primitives and encapsulate them is immense.
Much like primitives; whatever operation that needs to be done on the raw collection; once encapsulated; is available everywhere and is implemented exactly the same... because it MUST be the same.
What about some strange functionality that's way too specific to incorporate into the class? Needing to iterate over the items and check X, Y, and Z, but not all of them; just if one...
We don't want to just duplicate the LINQ functions onto our interface. This is a tough question because it is going to vastly depend on what needs to be done.
Should the collection implement IEnumerble
? Maybe. I've done that before. You can then access all those methods without having direct access. Not bad... but still allows a WIDE range of functions. There's little control. Including turning it into a List... and having a primitive collection.
Another way is to just expose ForEach
. Allowing the consumer to provide the lambda to operate on each element.
Or maybe it is a behavior that should be encapsulated in the object. Collections tend to be very system dependent on what behaviors they implement vs exposing the raw collection.
There is only the guiding principle - Minimize exposure.
I just remembered another way I've done it - Many Objects. When you want to perform some behavior, like filter - you have a "FilteredByItemCollection" object which will take in the list and filter criteria and then you have a new ItemCollection object.
... A flavor of the decorator pattern. Just keep decorating ItemCollections until you finally get the objects back that are configured.
This is my ideal way to do it. I've encountered times it was hard to get it into place cleanly. It keeps the details hidden from the system, but provides the flexibility. It also nicely aligns with representing the concepts. :) Funny how I end up there.
I tend to dislike suffixing with "Collection". It implies it implements the ICollection interface. I often just pluralize to create the class... "Items". But it's visually not distinct enough to parse while skimming.