I am developing some custom controls in an Object Oriented language (using Swift/Cocoa but this is a technology agnostic question). In particular, I have a horizontal and vertical set of buttons that behave very similarly except for the fact that one is oriented vertically and the other horizontally.
My question: what is the correct way to structure these two ‘things’ in my project:
Should I have a (abstract) parent class that has common functionality and then two derived concrete classes for the specifics needed for horizontal-ness vs vertical-ness. This feels the most correct but given that the object is composed of other objects you are then going to have proliferated throughout the view hierarchy going downwards.
Alternately, should I design one highly generalised class that has a property for orientation and then a lot of if/switch logic in the code to drive behaviour based on this. This feels like a code-smell.
Just go and create two separate similar but technically unrelated classes and just do a little more repeated typing. Since so many things are driven by the orientation this almost seems simpler than 1 above.
Or is there another alternative?
Other canonical examples where this would arise could be horizontal vs vertical:
– scrollers
– stacking panels
To attempt to clarify on regarding design goals and requirements, I would like the question answered in terms of how Apple Software Architects might have approached the NSScrollView class (or how any large organization might approach similar UI library code with “mirror” horizontal and vertical components).
The answer impacts several related vertical vs horizontal classes that aggregate these buttons into rows and columns respectively as the rows and columns themselves are also very similar save for the vertical vs horizontal orientation.
5
As a note up front, I am not familiar with how the Apple UI components commonly work and I mostly have experience working with Qt. This might influence the applicability of my answer.
I would start with a set of widgets that are orientation-neutral, but that can naturally be used where you expect an orientation-specific widget. For example a button where both dimensions can be set as fixed size or stretching and a grid with multiple rows and columns. These are concrete classes that can be used as-is.
With these orientation-agnostic widgets, a vertical button is just a button with a (relatively small) fixed-size width. And a row is just a grid with only one row.
How to realize this depends mostly on how the UI is typically being built.
- If the UI is mostly coded by hand, you can create orientation-specific derived classes that do nothing more than set the appropriate defaults of their orientation-agnostic base classes. A
VerticalButton
class would only have a constructor to set the button’s width as a fixed value.
This doesn’t prevent someone from changing the width back to being variable, but that would be their own fault. - If the UI is mostly generated from a graphical designer, I would look into the possibility of having the designer provide the widgets in multiple tastes (horizontal, vertical and orientation-agnostic) that instantiate each the same class with different settings.
If that fails, I can always fall back to the first option.
Only if the orientation-specific widget needs behaviour that can’t be provided by the orientation-agnostic variant would I consider making a full-fledged class (with more than a constructor) for the orientation-specific widget.
For example, if the text on a vertical button needs to be rotated, but the regular button class doesn’t support text rotation.
Answering in a very abstract and technology/framework agnostic manner, my thinking is as follows
- What a GUI control is (ie. volume up, volume down) not equates to how it is positioned in a window (a frame of visual reference).
- Therefore separate the functionality of laying-out from the controls themselves.
- Use a factory to create various layout managers for Controls (Control can be a common interface that all controls implements or provides – I suggest use composition – and provides information as required by the layout manager. For example a particular component may have a preferred width or height, similarly layout manager may want to instruct the control/component that it is now in a Horizontal layout thus dimensions are so..etc.)
- in this case a LayoutFactory.createHorizontalLayoutManager().layout(List controls)
What does this facilitate
- in the future you just need to modify / add / factories or methods in them to create more layout options.
- LayoutFactory.createHorizontalLayoutManager().layout(List controls) is a Control (various controls like Button, ScrollBar..etc) agnostic. It just knows how to layout a component/control.
- The control themselves are agnostic to target device (phone,TV,BillBoards,Laptop..etc)..that information can be incorporated into layout manager (even better, an concrete implementation of a Device interface would take a layout manager and it would know what a horizontal layout means in that particular device and render it accordingly)
two derived concrete classes for the specifics needed for horizontal-ness vs vertical-ness. This feels the most correct
Well, to me this feels mostly wrong. Your button can have a dozen properties or more like the orientation, for example it could be resizable, have a specific color, it could be labeled with an image, text or both, and so on. If you are going to create derivations for each property, even when you restrict yourself to properties which don’t change over the lifetime of the object, you will need an additional derivation for each combined property (for example, a VerticalResizableImageButton, HorizontalResizableImageButton, VerticalResizableTextButton, HorizontalResizableTextButton, each of those also in a non-resizable variant, and so on). Imagine how many different derivations this will mean in the end – each new property will multiply the number of needed derivations by a factor. I don’t think this is feasible.
should I design one highly generalised class that has a property for orientation and then a lot of if/switch logic in the code to drive behaviour
I would try to design one highly generalised class that has a property for the orientation, but as few conditional logic as possible. If the part of the code which depends on the orientation really ends up in many if/else statements, it can probably be encapsulated into one or more strategy objects, which are initialized once in the button constructor. For example, a “DragStrategy” (with derived classes “VerticalButtonDragStrategy” and “HorizontalButtonDragStrategy”) for controlling the drag-resizing.