Off topic, do you know where you got that phrase, "the a-ha moment": is it from a book, from someone else, or is it original? Because I think I remember someone's using that same phrase, when I was studying OOP and C++, online in about 1994.
Anyway: your question.
Like yours, my programming experience predates OOP: I did some Basic programming too, and, C.
It was, in a way, as you said, "a very linear approach to coding".
I was programmng professionally, in C, when I started to learn C++. So in terms of being able to "sit down, open up VS2008, and start coding", I was kind of already doing that, every day: I was sitting down and coding, in C.
It was event-driven programming: the software was reacting to events, to input: from the network, from various devices, from the UI ... so in that way, even this C code wasn't at all as linear as the kind of imperative Basic code ("read a keystroke, write to the screen") that we'd use to write.
So that's one thing: event-driven programming. Event-driven programming is not linear, it can be kind of difficult to conceptualize: a bunch of event-handlers, some independent, some interacting with each in a non-fixed sequence. Event-driven programming is not OOP (you can write event-driven programs in C), but one of the things that OOP is known to be good at is GUIs and event-driven programming. I suggest that OOP doesn't necessarily make it easy: the problem can be inherently difficult, and OOP makes it easier, not easy.
So that's another thing: if you can do it at all, then you're doing something right. Don't expect that everything will always be easy. A good solution might make the solution look easy and obvious, but developing a good solutions to large, difficult problems isn't easy, and we might thank OOP for making it possible to do at all.
Anyway, back to my story: I was coding in C. I learned incrementally to use C++, for example:
- I have a bunch of data (structs), and a bunch of functions which operate on that data ... put the functions in the same structs as the data which they operate on, and "functions" become "methods"
- Now that data has associated methods which are supposed to act on that data, make the data private so that other methods can't touch it directly ... encapsulation and/or information hiding
- Now that I have well-defined classes (some of which already contain each other as data members), start using inheritance to implement similarities and dissimilarities between different types
For me, one of the "a-ha" moments was that we were driving drivers for different kinds of fax device, which each had different low-level APIs. Using OOP we could write ...
- A simple, abstract class, which declared the simple, high-level API (like,
sendFax
, receiveFax
, cancel
, etc.) which we wanted all devices to support
- Subclasses, which used the API of the specific hardware to implement the abstract API inherited from the abstract base class.
I've just remembered something else. When I was learning C++, I read various books:
- Thinking in C++ to learn to read the syntax of C++
- Effective C++ to learn various canonical tactical mistakes to avoid when writing C++
Having read these, and with about 6 months' practice, I could use C++ as a better C.
One of the next books I read though was Design Patterns by the Gang of Four: and reading that book was an "a-ha" moment for me, it made me think, "if those other books showed me how to write in an object-oriented programming language, this book is showing me why I would want to."
So, there's a recommendation: read Design Patterns.
I've been talking about C++ and so on because that was the era in which I had my first a-has. I think it's relevent ... knowing OOP and C++ well, it took me less than a week to "sit down, open up VS2008, and start coding" in C# when that came along.