Skip to main content

Section 12.7 Top Down Design

There are two main approaches to program design: top-down and bottom-up .
As we discuss each, we will consider how they might be used to design a program that calculates the number of days between two dates like 3/4/2023 and 4/5/2024. The program will ask the user to enter two dates, then calculate the number of days between them, and finally display the result.
The first approach we will consider is top-down design. This method involves starting with the highest-level function, which outlines the overall structure of the program, and then breaking it down into smaller functions that handle specific tasks. Each function should be designed to perform a single task or a closely related set of tasks.
As new functions are identified, we should try to clearly specify what inputs they take and what outputs they produce. If we are fairly certain a given function can be implemented with a short amount of code, we can consider that part of the design finalized. That function will be a base-level function that we can implement directly and then use to build higher-level functions. If it seems like a given function is too complex to implement easily, we should stop and identify parts of the task that can be further broken up.
The strength of top-down design is that we start from the problem we want to solve and use that to identify the necessary building blocks. This reduces the chances that we design functions that are not actually necessary for the program.
However, top-down design works best when the programmer clearly understands the problem being solved and can easily identify the necessary steps to produce a solution. It can be difficult to use this approach effectively if the problem is not well understood or the high level task is so complex that each piece must be broken down many times before we get to something simple enough to consider a base-level function.

Example 12.7.1. A Top-Down Design.

We want to calculate the number of days between two dates. We will ignore leap years so we can focus on the process and not on special case logic for handling those.
As we identify tasks, we will think of them in terms of precondition (information required to start that task) and postcondition (information produced by that task).

(a) Getting Started.

A top-down design for our date program would start by identifying the high-level tasks:
  • Get dates from the console.
    • Precondition: none. There is no data we need before we can do this job.
    • Postcondition: two strings representing the dates entered by the user like "3/4/2023".
  • Calculate the number of days between two dates.
    • Precondition: two strings like "3/4/2023" and "4/5/2024"
    • Postcondition: an integer representing the number of days between the two dates.
  • Print result
The first and third tasks sound pretty simple - a few lines of code each. The second task sounds too complicated to implement in a few lines of code. So let’s turn it into a function. We will call the function int daysBetween(string date1, string date2). It will take the two strings and produce the integer.

(b) Refining daysBetween.

We need to think through the daysBetween function.
If we can convert both dates into a number of days since the start of year 0000, we can then find the difference between those two numbers. That is how we will do the work for daysBetween .
Note 12.7.2.
We will use β˜… to mark new chunks of code at each step of the process. Each time you see a list of function ideas, look for that symbol to find the new functions. Anything without that symbol is a function we already identified in a previous step.
We will also use ... to indicate details left out as we repeat earlier descriptions.
So our design now looks like:
β˜… A function: int daysBetween(string date1, string date2)
  • β˜… Turn date1 into a number of days.
    • Precondition: date1 is a string like "3/4/2023".
    • Postcondition: an integer representing the number of days since 0/0/0.
  • β˜… Turn date2 into a number of days.
    • Precondition: date2 is a string like "3/4/2023".
    • Postcondition: an integer representing the number of days since 0/0/0.
  • β˜… Find the difference between the two numbers.
    • Precondition: two integers representing the number of days since 0/0/0 for each date.
    • Postcondition: an integer representing the number of days between the two dates.
The last new step, finding the difference, sounds easy. But turning a date into an integer number of days sounds complex. So let’s give that a name: dateToDays. We will make it into a function that takes a string and produces an integer: int dateToDays(string date). We can use the same function to handle both dates.

(c) Refining dateToDays.

We now need to design the function dateToDays. If we take "3/4/2023" and break it up into 3 (month), 4 (day), and 2023 (year), we can use these values to calculate the total number of days.
So our design now looks like:
A function: int daysBetween(string date1, string date2). It will:
β˜… The new function: int dateToDays(string date). It will:
  • β˜… Get the month
    • Precondition: date is a string like "3/4/2023".
    • Postcondition: an integer representing the month part.
  • β˜… Get the day
    • Precondition: date is a string like "3/4/2023".
    • Postcondition: an integer representing the day part.
  • β˜… Get the year
    • Precondition: date is a string like "3/4/2023".
    • Postcondition: an integer representing the year part.
  • β˜… Calculate total days
    • Precondition: integers for month, day, and year.
    • Postcondition: an integer representing the total number of days since 0/0/0.
How hard is it to take "3/4/2023" and get the day part, or the month part as an integer? Not too hard. We can probably write that in a few lines of code (find the right part of the string, then use stoi on that part). But if we make those tasks into functions, it will be easier to test that they are working correctly. So let’s do that.

(d) Adding dateToDays helpers.

This version adds in some functions to help turn parts of the date string into functions. It looks like:
A function int daysBetween(string date1, string date2). It will:
The function int dateToDays(string date). It will:
β˜… Three new functions for date manipulation:
  • β˜… int getMonth(string date)
    It will get up to the first / character, then convert the substring to an integer.
  • β˜… int getDay(string date)
    It will get the second part (between the first and second / characters), then convert the substring to an integer.
  • β˜… int getYear(string date)
    It will get the third part (after the second / character), then convert the substring to an integer.
We won’t bother to break those down further. We are confident we can implement those in a few lines of code each.

(e) Finish dateToDays.

Reviewing our design, the only part that we haven’t thought through is β€œCalculate the total number of days”.
Using the days and years should be easy. We know each year is 365 days (recall that we are ignoring leap years) and each day is 1 day. But the months will be more complicated.
What would make the job easier? Well, what if I could ask someone β€œhow many days are there before month X starts?”. If I could ask β€œhow many days were there before the start of March” and get the answer 59 (31 for Jan + 28 for Feb), it would be easy to do the rest of dateToDays. My math could just look like: totalDays = daysBeforeMonth(month) + (day) + (year * 365)
So let’s add a function daysBeforeMonth that answers that question.
We will update int dateToDays(string date) to look like this:
  • Get the month using int getMonth(string date)...
  • Get the day using int getDay(string date)...
  • Get the year using int getYear(string date)...
  • β˜… Get the number of days before the given month using int daysBeforeMonth(int month).
  • Calculate total days
    • β˜… Precondition: integers for day, year, and number of days before the month.
    • Postcondition: an integer representing the total number of days since 0/0/0.
Note that we adjusted the precondition for the last task in dateToDays to assume that it will use the value produced by our new daysBeforeMonth function.
How hard is it to implement daysBeforeMonth? It just needs to return a fixed value for each month. We could implement that with a big if/else chain (if the month is 0, return 0; if the month is 1, return 31; if the month is 2, return 59; etc...).
At this point, each of the listed functions seems simple enough to implement on its own. So now we can start implementation!

Insight 12.7.3.

While reading the steps, you might have noticed that this method depends on β€œseeing” the solution clearly before we get down to the details. We need to have a strong sense of how we are going to solve each part of the problem to identify the functions we need to build.
Faced with a new problem, you may have to try to solve it by hand first to get a better sense of the path you want to take before you start designing a solution.

Checkpoint 12.7.1. Top-Down Design Exercise.

Arrange the final blocks representing tasks in a logical order. Use indentation to show what are subtasks.
You have attempted of activities on this page.