I\'m working on a fairly new project where I\'d like to share some config items
ID: 647880 • Letter: I
Question
I'm working on a fairly new project where I'd like to share some config items used by a JavaScript plugin. Specifically, the project is using jQuery dataTables and I want to find a good way of sharing config items.
At the moment, the code I've been given is fairly simple. It is roughly equivalent to:
// It might not be super-essential to read this
function TableSuperClass(tableElement, moreOptions) {
var options = {
pagination: true, // turn on pagination
iDisplayLength : 50, // pages are fifty rows long
lengthChange: false, // stop users changing length of pages
searching : false // disables searching
};
jQuery.extend(options, moreOptions); // merge moreOptions into options
jQuery(tableElement).DataTable(options); // we create a table with the extended options
// This is a super-handy method we'd like to use everywhere
this.repopulate = function usefulRepopulateFunction() {...};
}
function ObjectOwningTable() {
// this object does other things too
this.table = TableSuperClass({
// various column mappings for a particular table type and
// what to order the table on
columns : [...],
// implements row highlighting, partly based on column mappings
fnRowCallback : function() {...}
});
}
function AnotherObjectOwningTable() {
// this object does other things too
this.table = TableSuperClass({
// various column mappings for a different type of table, switched
// based on some logic inside this class
columns : someTest ? [...] : [...],
// implements ordering a different way
aDesc : [...] // aDesc is a legacy API call that lets you specify the col to order on
});
}
The problem is that I now have another sort of table. It is used in a print view. It has the same column mappings as ObjectOwningTable, it needs the super-convenient repopulate method and disabled searching from TableSuperClass, but it doesn't have pagination, as a printout has to show the whole data set.
I can think of two basic solutions:
Solve the problem with inheritance. Make TableSuperClass provides the handy repopulate interface and disabled searching, then a subclass with pagination, then call that in ObjectOwningTable and AnotherObjectOwningTable. For my print table, I call TableSuperClass with the same column mappings as used in ObjectOwningTable (maybe I extract them to a global constant or something).
Solve the problem with composition. I have a single DynamicTable that provides just the repopulate interface, then pass in a list of plain objects that are applied to the options in order. These plain objects all live on an enum somewhere, so I can see how they might clash as I write them, and allow me to mix-and-match functionality further down the line.
So, at the consumer level, the first solution would end up looking like
function ObjectOwningTable() {
this.table = PagedTable(FOO_REPORT_COLUMN_MAPPINGS);
}
function AnotherObjectOwningTable() {
this.table = PagedTable(someTest? BAR_COLUMN_MAPPINGS : BAZ_COLUMN_MAPPINGS);
}
function PrintObjectOwningTable() {
this.table = TableSuperClass(FOO_REPORT_COLUMN_MAPPINGS);
}
Whereas the latter would look like
function ObjectOwningTable() {
this.table = DynamicTable([
DATA_TABLES_OPTIONS.pagingFixedAtFiftyRows,
DATA_TABLES_OPTIONS.disableSearching,
DATA_TABLES_OPTIONS.fooColumnsAndHighlighting
]);
}
function AnotherObjectOwningTable() {
var columnMappings = someTest ? DATA_TABLES_OPTIONS.barColumnMappings : DATA_TABLES_OPTIONS.bazColumnMappings;
this.table = DynamicTable([
DATA_TABLES_OPTIONS.pagingFixedAtFiftyRows,
DATA_TABLES_OPTIONS.disableSearching,
columnMappings
]);
}
function PrintObjectOwningTable() {
this.table = DynamicTable([
DATA_TABLES_OPTIONS.disableSearching,
DATA_TABLES_OPTIONS.fooColumnsAndHighlighting,
DATA_TABLES_OPTIONS.disableSortingControls
]);
}
I'm not sure what to choose. The inheritance approach feels very problematic. It seems very inflexible, and I can see a lot of ways for future developers to 'trap' functionality in particular class taxonomies. I also think that long inheritance chains are harder to reason about than flat structures. But the compositional approach is a bit more verbose, and I think my co-workers might object to the repetition. Yet I think that mixing and matching is really handy.
There is a third option, which is to create a subclass of DynamicTable that binds the pagingFixedAtFiftyRows and disableSearching options, and is then called by the objects that use it, passing in their peculiar column mappings. But I wonder if this makes things more complicated, using both subclasses and mixins. I also wonder if it creates the temptation for other developers to add functionality to the subclass when they should really try and create mixins.
What would you recommend?
Explanation / Answer
Given just the two possibilities you present, I think the first, inheritance, solution is better, even though I generally don't think inheritance solutions are better.
If I understand the code correctly, you second solution involves just one type DynamicTable that has a whole slew of booleans in it to customize its behavior. This means that anytime someone needs a table with different behavior, they will have to go into DynamicTable to add yet another boolean and conditionally change the tables behavior based on the new boolean.
Better, I think, would be to follow the Open/Closed Principle. DynamicTable should employ judicious use of the Strategy pattern such that your various other types can customize its behavior by injecting blocks of code into it, rather than simply setting booleans.
For example, you have the notion of pagingFixedAtFiftyRows. Presumably, you also have the notion of paging not being fixed at fifty rows, and that also opens the door to the idea of paging being fixed, but at something other than fifty rows. Expressing the notion of having pagingFixedAtFIftyRows as a boolean is kind of silly in this regard. At minimum, it should be an integer (where 0 would mean paging is not fixed.)
Another example, you have the notion of disableSearching. Presumably, in the DynamicTable you have at least one if statement that checks this bool and does something different depending on whether or not it is true. Better would be to have a separate Search Module that contains the conditional code. If a user wants searching, they inject the search module into the table. This also opens the door for them to do other interesting things to filter the output of the table that isn't exactly like searching.