0

I am an Electrical Engineer now training and working as an embedded software developer, so I have little formal computer science and software design training. I have until a few months ago only worked in C and functional programming, but started recently working with C++ and OOP for Embedded Linux with a team whose development guidelines I question. Performance is not an issue is this project.

I have to use classes as interfaces in our submodules, i.e. a library interface header declares a class that should be instantiated by the processes that use it (at the moment no other library needs it), and whose methods compose the API. I also cannot use global variables, i. e. the class has to encapsulate all data that it may need. I had never seen such a thing - I am used to building and using APIs with "standalone" (to the lack of a better word) functions (init(), open(), get/set/reset(), doSomething(), ...), having some global variables with file scope, and seldom some extern(I am aware the use of globals and extern may be frowned upon depending on the situation and am OK with my use of it). The problems I have found so far with this approach:

  • Conditional access: usually I created a mutex variable with file scope, initiated it in the init() function and used it in the getter/setter and where else needed. Now I have a class modifying an external resource that will be instantiated in multiple processes. Can in such an architecture a conditional access system be implemented at all? At the moment my solution is to have a dedicated task to deal with the shared resource and processes requests from the objects sequentially (the objects will limit themselves to sending the request to the task).

  • Code repetition: since the classes are hermetic there are some functions and data that got repeated. For example I have three classes translating related (but different) input values and then writing the output to a database. The db write function looks very similar across the classes and in my previous approach I would have created a single standalone function to handle those operations. I could construct a subclass for this, but I think the added complexity and difficulty to read does not justify it in this case, especially considering that is not the practice in our code base. If I were to adopt that route I would, for this relatively simple feature, have 3 subclasses implementing only one method - and that is too pedantic in my opinion.

My approach before the code was reviewed was the API with standalone functions and I used classes in the library internals to try and capture the relations between the data. As a (simplified) example I had to go from:

/*+++++
file translation.cpp
++++++*/

class translation1{
    int CurrentTranslation;
public:
    void translate(int);
    int getTranslation();
};
class translation2{
    int CurrentTranslation;
public:
    void translate(int); //translation is different from translation1
    int getTranslation();
};
...
class translation1 Translation1;
class translation2 Translation2;
...
void init()
{
    ...
}
void translateAll(int one, int two)
{
    Translation1.translate(one);
    Translation2.translate(two);
    ...
}
saveToDatabase()
{
    one = Translation1.getTranslation();
    two = Translation2.getTranslation();
    ....
}

to:

/*+++++
file translation.h
++++++*/

class translation1{
    int CurrentTranslation;
public:
    translate(int);
    int getTranslation();
    saveToDatabase();
};
class translation2{
    int CurrentTranslation;
public:
    translate(int); //translation is different from translation1
    int getTranslation();
    saveToDatabase();
};
...

I find the last approach contaminates the class with methods unrelated to the data.

Being new to OOP I feel I am being molded and tainted by an approach to the paradigm that I feel is not correct, so I am looking to understand how does it relate to the industry accepted practices and, on a secondary level, with the industry best practices. What other limitations can I expect from this development approach?

Do my struggles stem from the change to OOP, from bad development guidelines or are limitations of the OOP paradigm itself?

5
  • My limited experience with C++ restricts my ability to provide a comprehensive answer, but it seems to me that your guiding principle should be to treat your classes as small programs. Then you can have all the "global variables" and "standalone functions" you want; they will just be contained inside your classes. If you need some dependency from the outside that the class requires (including a mutex), pass it in as a parameter into the constructor of your class. If you need to pass something to the outside world, return it from a member function of the class. Commented May 24, 2018 at 15:17
  • I say this with the caveat that your classes should probably be "smaller" than the smallest of your original programs, and should each have a single purpose or theme. Note that the use of a header file to establish an API is not all that unusual. Also note that shared ("free") functions are very easy to implement in C++, and have a very high degree of cohesivity if they are written properly (input to output, no side-effects). Commented May 24, 2018 at 15:18
  • I think you are in need of a crash course in object oriented design and programming.
    – esoterik
    Commented May 24, 2018 at 19:23
  • @esoterik for programming already did. Can you suggest any course or book for design?
    – calofr
    Commented May 25, 2018 at 6:32

1 Answer 1

1

Your example doesn't elaborate on your approach versus the "required" approach.

One thing I can point out is that saveToDatabase() is not properly a part of the translation process, as you have intuited. Your original translation classes have one responsibility, to perform some sort of translation. Saving to a database is a separate responsibility, and should likely be performed in an orchestration function from a separate class.

However, the translation classes should be responsible for aiding the saveToDatabase() functionality by providing a serialized form. In your example the int result is the serialized form, but it could also be a string or a binary encoding.

Conditional access to resources can still be performed, but the scope and management will be different. Imagine that you have two objects instantiated, both of class translation that differ only by an initialization parameter. If there are no global or shared resources, making the mutex static would cause interference between the two objects where none is required because they have no shared state.

On the other hand, if there is shared state between translation objects, you likely want to extract that into another object that can then be used by the translation objects to properly share state. Saving to the database is a good example, so having an object that manages access to the database that is separate from the individual translation objects makes for much more maintainable code.

When you have boilerplate or repeated code, you can address it in several ways. You can create a common base class that implements most of the functionality that is held in common. Or you can create companion classes that the translation classes can delegate to.

1
  • Your answer has given me insight on how to approach these issues. Thank you
    – calofr
    Commented May 25, 2018 at 6:33

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.