Let’s assume a UI where currently we show students details on tiles. One tile per student. Now we need to refactor our code to support a new requirement. The new requirement is to change the order of those tiles based on different kind of sorting like – sort by student’s names, sort by student’s grades, sort by their age etc. Here, the sorting criteria will be selected by user on the run time through UI & user should be able to change the sorting criteria whenever he requires.
Right now, we have a list of objects containing student data which contains the required information to show on the tiles. We don’t put any logic on changing the order of tiles & show them in the same order we get it from some other service. To change the order of tiles, I would need to change the order of those objects accordingly because we render tiles on the basis of order of those objects.
Summarizing the requirements –
• User should be able to sort on the basis of different criteria on the run time. Several sorting criteria will be shown to the user.
• User should be able to switch to any sorting criteria or go back to unsorted state at any time.
• Sorting criteria can be many in future. There is no limit to that.
• All this to be implemented in JS.
I am considering this optimization as well–
• Since user can go to any state on his will, I don’t want to call our sorting framework when user is selecting same sorting criteria multiple times. It should be called for the first time only & later if he comes back to same sorting criteria, we shouldn’t be calling our framework again for giving us the sorted order. Instead we should store it somewhere on the first time & keep on using it later on. Eg. - Let say we rendered the tiles in unsorted order. User selects “sort by age” option. We call our framework & show the reordered tiles on the basis of its output. Now user selects some other sort criteria let say “sort by grade” & we call our framework again & reorder tiles based on that. Now user again selects “sort by age” option. Now we shouldn’t be calling our sort framework to sort on the basis of this criteria again to give us the results. Instead we should have saved this data somewhere in the first place so that it can be reused.
Sorting framework is also being created at the same time & is owned by us only. So, we have flexibility to decide the type of inputs & outputs to the framework. However regarding the sorting logic & design, let’s consider it a black box since there I have already decided to go with decorator pattern.
But looks like code changes on the feature side is going to be messy since possibilities for new sorting requirements is endless & we have to maintain the object states of each for reusability. I am brainstorming on several questions like
• How & Where to store the “sort criteria” to its corresponding data which is going to be the input of the sorting framework. Eg. – Let say I want to sort on the basis of age. I would need to get this data & make a map of “sorting criteria to age” for each student. So, I would create a “Map<Student, Map <SortCritria, respectiveData> > This is straightforward approach but can there be any cleaner approach since sort criteria can be many. Also, the big question is where should this logic be placed
• What should be the output of sorting framework. Should it return <oldIndex to newIndex> map or <uniqueStudentId to newIndex > map or anything else.
• Should I introduce a new layer between (eg. Store) our feature & sorting framework or
• should object states be stored in framework or in the feature code base. Etc.
I am looking for a clean approach or a design pattern where all these requirements can fit in. Any suggestions would be greatly appreciated.
Avoid premature optimization. That's what you are doing now with your "I shouldn't have to call the sorting framework multiple times for the same sort order." Create the simplest solution first. If after that you still have a concern about performance, use a profiler to see where you can optimize. DO NOT optimize based on your gut feelings or intuition: you will most likely be optimizing in the wrong places that way.
OK. First, get your basic architecture nailed down. There's a lot of verbosity there, but the 2 primary elements there seem to be "tiles", which we'll consider to be Objects, and a sorter.
In many languages, the same sorter can be used for all the different sort options simply by plugging in an appropriate Comparator, which greatly simplifies things.
As far as sorting optimally, now, that's a different kettle of fish. Junilu is correct. A good deal of my career has been to ensure high performance from software, and one thing I've learned over a very long and evil career is that not only are the true bottlenecks not where you "know" they're supposed to be, they're not even where you'd imagine that they'd be.
One of my favorite optimization examples to cite is a system that ran on a large IBM mainframe about 100 times a day. One of its core functions was to collect output from its components and sort that output. Now we all "know" that the fastest and most efficient sorts are HeapSorts and QuickSorts. Except that the data set in question was almost already sorted. Just a few elements that appeared slightly past the middle point. Well-organized data is a disaster for the partitioning sorts, since everything ends up in one partition and that partition gets subdivided into 2 partitions, one containing 1 item and the other containing all the other items. And so forth.
So the most efficient sort for this particular type of data was a Shell-Metzner sort. The partitioning sorts, as I said, would have been seeing their worst cases, and a simple bubble sort would have to percolate those few records item by item from near the middle to the upper end. The Shellsort is a binary-bubble algorithm, so the out-of-place elements swiftly percolated to their correct locations, needing perhaps a half-dozen passes for over 1000 records.
An IDE is no substitute for an Intelligent Developer.