views:

268

answers:

3

For an assignment I've made a simple C++ program that uses a superclass (Student) and two subclasses (CourseStudent and ResearchStudent) to store a list of students and print out their details, with different details shown for the two different types of students (using overriding of the display() method from Student).

My question is about how the program collects input from the user of things like the student name, ID number, unit and fee information (for a course student) and research information (for research students):

My implementation has the prompting for user input and the collecting of that input handled within the classes themselves. The reasoning behind this was that each class knows what kind of input it needs, so it makes sense to me to have it know how to ask for it (given an ostream through which to ask and an istream to collect the input from).

My lecturer says that the prompting and input should all be handled in the main program, which seems to me somewhat messier, and would make it trickier to extend the program to handle different types of students.

I am considering, as a compromise, to make a helper class that handles the prompting and collection of user input for each type of Student, which could then be called on by the main program. The advantage of this would be that the student classes don't have as much in them (so they're cleaner), but also they can be bundled with the helper classes if the input functionality is required. This also means more classes of Student could be added without having to make major changes to the main program, as long as helper classes are provided for these new classes. Also the helper class could be swapped for an alternative language version without having to make any changes to the class itself.

What are the major advantages and disadvantages of the three different options for user input (fully encapsulated, helper class or in the main program)?

+1  A: 

I think that what your teacher meant to say was "don't put it in the Student Classes".

As Vlad mentioned the models would be the student classes. The view should not be in the student classes. The idea is that the student classes should store structural information about those object. How that data is presented is up to the things using the class. If you were, for example, to use these classes later for both a console application and a GUI application, you would not want to have the display code in those classes. That should really be up to the application using the classes.

The view/controller would be in the helper class or the main program. Being in the main program does not mean that it has to be messy. You might have alot of functions to make the main() look nice and clean, but the same would be true if you write it in a helper class. You'll have all those functions and maybe a few more.

What I would suggest is that if this is a small exercise , don't add the helper class unless you already have a clear idea as to how that class would be, or if you have the time to spend on figuring out.

Adding the helper class is fairly straightforward - I already have all the functions for it, so it's mainly just a matter of changing the references so that they point to the helper class instead of the Student class, and tweaking.The assignment specifies that the Student should have a display() function that is overridden in the subclasses, so I will keep display() in the Student class, but recognise that this is not ideal. To do this more properly later, would I make a helper class for student that handles display, and extend this helper class for each subclass so I could still override?
Dr. Monkey
+1  A: 

As mentioned by scv, it is normally better to decouple presentation (view) from internal structure (model).

Here you have a typical case:

  • the Student class, root of a model hierarchy
  • the Displayer class, root of another independent hierarchy

The issue with the display is that it varies according to two elements, which calls for a system of double dispatch (using virtual).

This is traditionally solved using the Visitor Pattern.

Let's check the base classes first:

// student.h
class Displayer;

class Student
{
public:
  virtual ~Student();
  virtual void display(Displayer& d) const = 0; // display should not modify the model
};

// displayer.h
class Student;
class CourseStudent;
class ResearchStudent;

class Displayer
{
public:
  virtual ~Displayer();

  virtual void display(const Student& s) = 0; // default method for students
                                              // not strictly necessary
  virtual void display(const CourseStudent& s) = 0;
  virtual void display(const ResearchStudent& s) = 0;
};

And now, let's implement some:

// courseStudent.h
#include "student.h"

class CourseStudent: public Student
{
public:
  virtual void display(Displayer& d) const;

};

// courseStudent.cpp
#include "courseStudent.h"
#include "displayer.h"

// *this has static type CourseStudent
// so Displayer::display(const CourseStudent&) is invoked
void CourseStudent::display(Displayer& d) const
{
  d.display(*this);
}


// consoleDisplayer.h
#include "displayer.h"

class ConsoleDisplayer: public Displayer
{
public:
  virtual void display(const Student& s) = 0; // default method for students
                                              // not strictly necessary
  virtual void display(const CourseStudent& s) = 0;
  virtual void display(const ResearchStudent& s) = 0;
};

// consoleDisplayer.cpp
#include "consoleDisplayer.h"

#include "student.h"
#include "courseStudent.h"
#include "researchStudent.h"

void ConsoleDisplayer::display(const Student& s) { }

void ConsoleDisplayer::display(const CourseStudent& s) { }

void ConsoleDisplayer::display(const ResearchStudent& s) { }

As you can see, the hard part is that if I wish to add a new derived class of Student, then I need to add a new virtual method in Displayer and override it in every derived class of Displayer... but otherwise it works great.

The advantage is that the logic of display is now decoupled from the model, thus we can add new display logic without ever touching our model.

Matthieu M.
+1  A: 

Hi Dr Monkey,

While I am genetically skeptical to my advisors, I think your advisor has a valid point here.

It might be too simplistic to realize how putting cin/scanf inside classes matter. But imagine this, your student class forms the back-end of some code with a GUI and the data comes from all sorts of things -- radio buttons for gender, combo boxes for age-group and so on. You really should not put all of this inside your student class.

Having a 'viewer' or a helper class that populates student helps. I suggest have a class each depending on the kind of view. You could do it inside main, but having separate viewer classes will help you re-use code.

Arpan

Fanatic23
I've long since submitted this project, and as it turns out I did exactly what you've suggested here. +1 because I thought it was a good idea, and had the same sort of reasoning.
Dr. Monkey