So I do specialized programming in industry (which transfers over in some ways but not others) and I'm trying to get better at general-purpose programming for my own edification.
I built a basic little arithmetic calculator in Python using Tkinter for a GUI. I ended up having to use global variables to store my operator and operands because functions called by button clicks can't return anything for use by the rest of the program.
Now I'm told that global variables are the devil and I shouldn't use them if I can reasonably avoid it, but I don't see any good alternative even in this simple academic exercise with 100-ish lines of code. Even sites like Stack Overflow recommend using global variables in such cases, but it seems very weird to have to break the "rules" for something as trivial as clicking a GUI button.
I don't discount the possibility that I'm just retarded and taking a fundamentally wrong approach to what I'm doing. Does anybody have any insight on this?
Disclaimer: I don't like Python, and it's been a while since I've done desktop/mobile UI stuff. And by "arithmetic calculator" I assume you mean something similar to a basic pocket calculator that does addition, subtraction, division and multiplication, though I think these ideas will work with more complex things as well.
So what you basically need is something to store state. Pushing the buttons will add information (numbers and operations corresponding to the buttons pushed) to the state storage. Pushing the "equals" button will kick off a process which takes that state, does the calculation, and then prints the result to the "screen." Let's call this container to store state OperationStore. We are only going to instantiate one of those - a singleton.
Code:
let opStore = new OperationStore()
Now we're also going to add event listeners to the calculator buttons which put state into the store. When we instantiate it, we're going to give it a reference to opStore to do its stuff to.
Code:
class CalculatorButton extends UIButton {
private OperationStore opStore
private String buttonLabel
private UILabel screen
// Hey, look, it's dependency injection!
func init(OperationStore opStore, UILabel screen, String label) {
this.opStore = opStore
this.label = label
this.screen = screen
}
func onPush() {
this.opStore.pushOperation(label)
screen.setText(screen.getText().append(label))
}
}
let screen = new UILabel("0")
window.addControl(screen)
let button1 = new CalculatorButton(opStore, screen, "1")
window.addControl(button1)
let button2 = new CalculatorButton(opStore, screen, "2")
window.addControl(button2)
…
So, wait. We're just passing off opStore to everything. Is that really better than a global variable?
Well, yes. Let's take the "screen" of the calculator, for example. It doesn't need to have a copy of opStore since it will never need to access it. So no code related to the "screen" will have access to opStore. But the parts that
do have to access it can do so.
For a clearer example, say you added an MP3 player to your application. The MP3 player would need to have access to opStore, since it just plays audio files, and it would have its own buttons to do so. Yes, it doesn't make much sense for an application to both play MP3s and work as a calculator, but hopefully it illustrates other cases where several parts, but not all parts, of an application might need to share a bit of data, and it makes sense to isolate the data away from those parts that don't need it.
Just to finish out the example, here's how the "equals" button of the calculator might work. I was lazy and put the logic that actually does the calculation there too - maybe that's a good idea, and maybe it's not.
Code:
class equalsButton extends CalculatorButton {
func onPush() {
Float total = opStore.doCalculation()
screen.setText(total.toString())
}
}
Now it's time for others to tell me how shitty this structure is and how some other approach is better.