A Few Basic Principles For System Design (Part 2)
Building complex systems is never simple, but there are a few things you can do to make your life a bit easier. One of the hardest parts in designing these things is to make them testable, so that work tomorrow doesn’t break work today. I’ll go over a few concepts to make life easier when doing this.
Composition - UI
Composition isn’t just a tool to use when writing code and classes. You can build UI widgets via composition as well.
Let’s look at an example from a project I am building. QML is a markup language for expressing graphical views in the Qt engine (C++).
In this project, I need a way to capture time logs on a day-by-day basis. The business specifics are unimportant, but I for sure need to express an individual day’s information.
So let’s make a DailyLogRow
view and it’s associated controller.
import QtQuick 2.4
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.2
Item {
property date day
property alias mouseArea: mouseArea
property alias directLabel: directLabel
property alias indirectLabel: indirectLabel
property alias superLabel: superLabel
property alias dayLabel: dayLabel
signal dayClicked(date date)
MouseArea {
id: mouseArea
anchors.fill: parent
Row {
id: rowLayout
width: parent.width
height: parent.height
spacing: 5
Text {
id: dayLabel
width: parent.width / 3
text: "day"
}
Text {
id: directLabel
horizontalAlignment: Text.AlignRight
width: parent.width / 6
text: "d"
}
Text {
id: indirectLabel
horizontalAlignment: Text.AlignRight
width: parent.width / 6
text: "i"
}
Text {
id: superLabel
horizontalAlignment: Text.AlignRight
width: parent.width / 6
text: "s"
}
}
}
}
import QtQuick 2.4
import us.parich.DailyLogApp 1.0
DayLogRowForm {
DailyLogSerivce {
id: svc
}
mouseArea.onClicked: {
dayClicked(day)
}
function setDate(newDay) {
day = newDay
var model = svc.getLogByDate(day)
dayLabel.text = day.toLocaleString(Qt.locale("en_US"), "d") + " - " + day.toLocaleString(Qt.locale("en_US"), "dddd")
directLabel.text = model.direct + "d"
indirectLabel.text = model.indirect+"i"
superLabel.text = model.super+"s"
if(model.direct > 0 || model.indirect > 0 || model.super > 0) {
visible = true
height = 30
}
}
Component.onCompleted: {
}
}
In this example, the DailyLogService
via the svc
property is a backend data contract that we can request day-indexed information from. We also announce to any consumer that the row has been clicked on via the dayClicked
signal.
Let’s make a week of it!
Item {
ColumnLayout {
RowLayout {
id: labelRow
Text {
id: weekLabel
text: qsTr("This Week")
font.pointSize: 18
}
Item {
// spacer
Layout.fillWidth: true
}
Button {
id: weekShowHide
}
}
Column {
id: column
DayLogRow {
id: satLogRow
}
DayLogRow {
id: friLogRow
}
DayLogRow {
id: thuLogRow
}
DayLogRow {
id: wedLogRow
}
DayLogRow {
id: tueLogRow
}
DayLogRow {
id: monLogRow
}
DayLogRow {
id: sunLogRow
}
}
}
}
Now we have 7 self-contained daily log rows in each week element. In this way, a UI element is built out of the behaviors it should contain, instead of conditional rendering of each specific user interaction.
An additional benefit of this is we can make a simple container page that embeds only 1 of an element and test it rigorously.
Item {
property alias test: test
DailyLogRow {
id: test
}
}
I’ll leave this as an excercise to the reader on applying this principle to other UI systems (tcl, html, xaml, swing).