Get ready to dive deep into the world of object-oriented development and design as we explore how beginners can get started with objects. So, buckle up as we explore the exciting realm of object-oriented programming and design.
Now, imagine this: you’re a Java developer with over 20 years of experience under your belt. You might think that all Java developers are seasoned professionals who effortlessly sketch UML diagrams on whiteboards and discuss object interactions to craft the perfect design. Well, let me tell you, that’s not entirely true.
Here I’ll break down how I get started fleshing out code and how I evolve it into objects…
Embracing the Minimal Approach
Ok, so first off when I start a new project from scratch, I don’t immediately jump into designing intricate objects and breaking down the system. Nope, my initial focus is on the bare minimum, the smallest unit that gets the job done. I’m talking about putting everything in a single solitary public static void main method without worrying about breaking it down further or designing objects. That comes later.
But here’s the cool part: by starting with this minimal approach, I get to concentrate on the essential functionality. I don’t have to worry about decomposing calls or thinking about object interactions. It’s all about getting things to work by including the necessary dependencies and coding up the required functionality.
This let’s me focus on having a “quick win” — something that I can see up and running, doing what it needs to do and seeing that, in turn, gives me momentum: I know I’m moving in the right direction and I can completely see that I’m going to nail the end goal.
Example: coding up a report generator
Let’s take a hypothetical example we can work through together — and I’ll explain how I go about it…
Prepping your coding mindset
Suppose I have to write some code which can generate a report in PDF or Excel format, I first focus on generating the PDF or Excel file right from the start. I don’t get bogged down with trying to pluck out domain concepts out of thin air at first and try and fabricate some relationships between them just so I can say I’m in OO land — so I’m not even thinking about inheritence, polymorphism or even encapsulation at this point— it’s just too abstract a thought process for that approach to turn out anything concrete.
And this is where a lot of developers new to the world of OO get stuck: by trying to immediately code in this OO land from the outset. But listen, you don’t have to go down that route. And in fact, you shouldn’t: you should just focus on getting the job done in the smallest most compact set of method calls possible — we can worry about objects later on. (I hope that’s a freeing thought for some folks! ;))
Step 1 — Create the smallest possible unit of code which works
So in my hypothetical example, I’d be using and getting to know libraries like Apache POI to handle the different file formats. I’d have a look through the examples in the user guide, have a bit of a play to see if I can do the same thing in the IDE — just focusing on getting the library to do the smallest and simplest possible thing initially, like loading a file for example. It doesn’t have to be sophisticated at this stage — in fact the opposite is better, just move forward one small baby step at a time.
Once I have this in place, and I know I can use the library (i.e. my dependencies are good in my build file if using Maven or Gradle), I start evolving the code then: making API calls, navigating workbooks and worksheets, adding cells and formulas, whatever needs to be done. All the way, I’m working one baby step at a time until I get to where I want to be functionally speaking.
The key point is to start off by getting something together which is the smallest possible unit of code, a single method, which does what you need to do.
Step 2 — Baselining your MVP code routine
Then when I have that method doing exactly what I need it to do and have done that figuring out how to integrate third-party libraries phase, I take a step back and assess the situation. I create a copy of the project or set up version control using Git. This gives me a foundation to build upon. At this point I’ve got a distilled down version of the minimal set of method calls needed to do what I want to do. So this is a great base to start our OO journey from. But before we do that, we do want to have that version-control safety net in place which is why we set up a Git repo for the code. Knowing that we can always get back to that super simple and condensed down MVP (minimum viable product) version of that code- well, it gives us the mental freedom to explore, play and have fun with the design. We ‘re then free to move as we want, refactor to the max and know we’re always safe that we can roll back if our little explorations didn’t quite go as we planned (sometimes you can only see how good a design is once you’ve evolved the code and you can jump through it in the IDE, so don’t worry!)
Step 3 — Bring in the objects, one at a time
Now, at this stage, we haven’t even talked about objects in depth. We’ve merely focused on getting the core functionality right. And up to this point, it’s been just coding the routine procedurally. But to transition from this initial, procedural based code to a more organized and object-oriented design, we start to take some baby steps in that direction. So I usually start by taking that method we created and move it to its own class.
Let’s call it the ReportGenerator class. We migrate the code into a method inside this class, which might be called generateReport. This class represents the functionality of generating reports. However, we’re still not dealing with proper objects yet because we haven’t introduced any state.
You see, initially, all the variables and data were scoped inside the main method. By moving the code into its own method within ReportGenerator, we’re just wrapping it up in a class that represents what it does. At this point, the method is static since it doesn’t have any non-static data members. It’s a simple encapsulation of the code.
We now need to think about the goal of the method. What is it trying to achieve? Is it a single thing it’s trying to do or is it trying to achieve multiple things? We’re leaning towards the single responsibility principle here. Our goal is to make sure that we end up with classes that do one thing, only one thing and do it well. So we break down the method further into functional areas.
As I go along doing this, I’ll start to decompose that single main method we started out with, which is now in a separate class, into other methods which make sense. This is known as “stepwise decomposition” and it’s a principle borrowed from procedural programming, but that’s ok at this stage — remember, we just want the smallest possible code to get the job done, fanned out into some nice logically-named method calls. And it’s from this that we can start to evolve it into an OO design.
For instance, in our ReportGenerator class, we have two main functional areas: generating a spreadsheet and rendering a PDF. Each of these areas will have its own method initially which is the main responsibility of that class. We interact with the Apache POI library for the spreadsheet generation, while the PDF rendering involves different strategies and libraries.
Breaking down the method like this allows us to focus on the distinct functionalities we want out of our code. And as we pull out these different methods we consider if it’s worth pushing those into their own classes too. This decision is based upon separating out responsibilities so that all class es only ever have one clear purpose (that’s that single responsibility principle we mentioned earlier), but also can be based upon considering what state is needed: i.e. does the object need to hold it’s own state — i.e. we’re considering encapsulation now where an object is responsible for managing it’s own data. We’ll also start to consider other OO concepts too during our coding: maybe we can use inheritence to get reusability or perhaps we can use polymorphism to get different types to behave differently through the same interface — those kinds of things too.
So we push on as we do this, bouncing from allocating responsibilities amongst the classes we’re creating, employing the object oriented constructs we’re aware of, whilst ensuring we evolve the functionality we need — perhaps there’s something we didn’t quite have up front in our initial implementation, or we discover a “nice to have” which we can slot in to improve our code. So in terms of our example, we might at this point start thinking about the need for data input into the process for example. This leads us to consider creating another class responsible for handling the input data. It’s a separate functional area so it needs a separate class (but again in turn, this might be fanned out into other classes if required).
It’s really about a balancing act between allocating responsibilities to the objects we create, while maintaining and even evolving the initial functionality.
Small Steps, Big Changes: Gradually Evolving the Design
So far, we might have the ReportGenerator class with the generateReport method, a DataReader class for input data, a SpreadsheetCreator class, and a PDFRenderer class say. Each class represents a specific functionality. And remember, we haven’t introduced objects with state yet. We’re keeping it simple for now.
But as we progress, we’ll start thinking about objects and how they can hold state. We’ll add member variables to classes to manage data and separate method-specific state from object-specific state. We’ll continue breaking down the functionalities and distributing code across these classes and so it continues.
As we go further, and as you get more experienced in OO concepts and more skilled in your OO journey, we’ll start exploring some design patterns we might introduce, start thinking about layering in our architecture, and all those other fancy concepts. But don’t worry if it seems overwhelming right now.
Start with the basics and evolve your design gradually, doing what you can today.
You don’t need to hit on a perfect OO design model from the outset, instead it’s all about taking small steps and making changes for valid reasons, to get to a nicely laid out codebase which uses OO concepts through being purpose-driven and intentional, rather than just for the sake of using them from theory.
So take care, have fun and know that just doing your best with what you know currently about OO will free you from the blank slate syndrome of not knowing where to begin coding with objects.
💥 Want more? Go get the FULL COURSE here: 👉 Java for Beginners 💥
►► Grab our FREE beginners guide to Java if you’re completely new to Java!
👉 Check out our courses at https://courses.javaeasily.com/courses 👉 Listen to our podcast at https://spotifyanchor-web.app.link/e/xjAOXisFABb 👉 Visit out our website at https://javaeasily.com/ 👉 Read our articles on Medium at https://medium.com/java-easily