1

Suppose I have low level classes A, B, and C that are independent of each other. The functions in them take some input and does some computation and returns some output.

Now suppose I want to create an executable where it read in some data and output some statistics with the flow of

input->A.func1->B.func1->C.func1->output

There are two options for me to handle this,

  1. Create a high level class called ABC. It does no computation, it simply receives the input from the main function, parses the input and pass it into A.func1, and forward the output of A.func1 into B.func1, and forward the output of B.func1 into C.func1, and output it to the main function.

  2. In the main function, directly parse the input itself, pass it into A.func1 and then B.func1 and then C.func1 and then save the output.

If class ABC does some computation internally while calling functions from A, B, and C, it's a no brainer that ABC class should exist. However, I am having a hard time to justify the need of a ABC class since it's just calling functions from low level class without any additional computation. It also means if we want to create 10 executables that are using the functions from the low level classes with different combination and order, we will have 10 of these high level classes.

However, I heard people said having a high level ABC class is better because Why should main() be short?

Which approach is better?

3 Answers 3

2

Neither 1. nor 2. is always "better". Here is how I would typically make the decision:

  • start without an additional class ABC and call functions from A, B and C directly from main. If the orchestration logic is very simple, no need to change it.

  • as soon as the logic in main becomes more complex, needs unit tests or has to be reused somewhere else, refactor. For example "parsing" looks like a task on its own, it could be placed in a separate class.

  • as a result of the refactoring, you may end up with a separate class "ABC" (hopefully with a more descriptive name). Maybe you end up only with a class orchestrating A and B, a parsing class, some output processing class, and let the calls to C separated. This depends on the details of your situtation.

However, I would typically not make this decision up-front, I think it is much better to decide during the refactoring process. The driving forces here should be

  • forming better abstractions
  • testability
  • reusability.

For very simple programs, it is perfectly possible you have all the abstractions you need in main, don't need to write tests which require a separate class ABC, and you don't need to reuse anything. If that is the case, stay with option 2.

1
  • phenomenal answer! May 13, 2017 at 7:16
1

There is nothing here that convinces me that you need any classes at all. What you're describing is functional composition: a(b(c(x))) Yes the code can be that simple. If you're in a functional language.

But since you've mentioned classes no less that 12 times I assume you're in some kind of OO paradigm. Not sure if that's your language, your code base, or simply your mind set. OO can be functional as well. It just takes some work.

What you haven't once mentioned is an object. Near as I can tell A, B, and C have no state. I used to reach for static methods when in this situation in a OO language like Java or C# but I've learned that stateless objects are far more flexible than static methods. So rather than statically import A.f1 as a ... to allow for this code: a(b(c(x))) I create objects a, b, and c and use this code: a.f( b.f( c.f(x) ) ). Why? because I'm stuck in a language that won't treat functions as first class citizens. This way the order of the composition isn't hard coded. I could swap a and c if I liked. This works around not being about to swap the functions directly like you can in a language with first class functions.

Which brings me to your question about main. I like option 1. I'd like a better name than ABC though. I would want to build objects a, b and c in main and pass them to ABC when I build it in main. Only once all this construction is done do I then start using what I've built with one call to abc.f(x).

My preferred way of doing dependency injection is good old reference passing but following a pattern of separating use from construction .

If I had chosen option 2 I'd be looking at a.f( b.f( c.f(x) ) ) directly. Which might not seem that bad but it's the first step to doing everything procedurally in main. I like to put things in clearly labeled boxes rather then let them scatter about.

Rob Kennedy's point about reusing the high level class function is also valid. Main isn't exactly the most reusable construct.

2
  • The is mainly about C++. Lets assume A, B, and C are stateless and they all have multiple public methods. I just happen to need to create an app that will use some of the methods from them. Interesting that you like option 1 better, if you go with option 1 I think building objects A, B, and C in main is a bad idea, because the main advantage of the abstraction to ABC is there is no need to include A.h B.h and C.h. I came across this stackoverflow.com/a/4668400/3667089 which seems to support option 2. What do you think? May 13, 2017 at 5:13
  • I think you're free to suck all the details into ABC if you like. But I still argue that mixing construction and use together is a bad idea. You've already mentioned a reason why. It reduces the re-usability of ABC. If ABC hard codes a b and c then that particular combo will be all it's ever good for. Not all construction has to be done in main. But it'd be nice if main didn't only have one kind of construction to pick from before using ABC. You can still separate these without doing the work in main. That's what construction patterns are for. May 13, 2017 at 5:27
0

I see the following abstractions in your post.

  1. Application Data -- possibly divided into Input Data and Output Data
  2. Input Reader
  3. Compute Engine - ABC
  4. Helper classes of ABC - a, b, and c.
  5. Output Writer

To implement what you are shooting for, main can be as simple as:

int main()
{
   InputData input( ... );
   InputReader reader(...);
   int status = reader.read(input);
   if ( is_failure(status) )
   {
      // Deal with error.
   }

   OutputData output;
   ABC abc(...);
   status = abc.process(input, output);
   if ( is_failure(status) )
   {
      // Deal with error.
   }

   OutputWrite writer(...);
   status = writer.write(output);
   if ( is_failure(status) )
   {
      // Deal with error.
   }
}

To make main more flexible, you can move all of them to anther function, such as:

int myAppMain1()
{
   InputData input( ... );
   InputReader reader(...);
   int status = reader.read(input);
   if ( is_failure(status) )
   {
      // Deal with error.
   }

   OutputData output;
   ABC abc(...);
   status = abc.process(input, output);
   if ( is_failure(status) )
   {
      // Deal with error.
   }

   OutputWrite writer(...);
   status = writer.write(output);
   if ( is_failure(status) )
   {
      // Deal with error.
   }

   return status;
}

int myAppMain2()
{
   // Similar to myAppMain1
}

int main(int argc, char** argv)
{
   // Based on options passed to the program, you may use
   // myAppMain1() or myAppMain2().
   return myAppMain1();
}

I think that is very clean and does not make main or myAppMain* any more complex than they need to be. myAppMain* may live in different files to keep main.cpp very small and uncomplicated.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.