0

As software design paradigm which is better? to let various methods manipulate a member variable, or define each method or function to take some inputs and provide some outputs?

For example

   class Test {

       void FooMethod1()
       {
           Foo = ...
       }
       void FooMethod2()
       {
           Foo = ...
       }
       void Method3()
       {
           FooMethod1();
           FooMethod2()
       }
    }

versus.

  class Test {

       var FooMethod1(var input)
       {
           input = ...
           return ...
       }
       var FooMethod2(var input)
       {
           input = ...
           return ...
       }
       void Method3()
       {
           var Foo;
           FooMethod1(Foo);
           FooMethod2(Foo)
       }
    }
Ahmad
  • 1,806
  • 3
  • 20
  • 32
  • one may argue that conceptually, this has been addressed in [How would you know if you've written readable and easily maintainable code?](http://programmers.stackexchange.com/a/141010/31260) If your peers keep complaining about your way of doing things, be it one way or another, you better change to make them feel better – gnat Dec 03 '14 at 16:15
  • 2
    As most of these things go, the answer is that it depends. It depends on what it must do, if you want a class to hold state, if it is going to be inherited. Thus this question cannot be answered as is. – Neil Dec 03 '14 at 16:17
  • There's not enough info here to answer the question, but as a general rule, globals are bad. Making parts of code only work when certain other parts have executed makes it harder to reason about, makes it very easy to make mistakes, and introduces unnecessary dependencies. – Doval Dec 03 '14 at 16:19
  • @Neil, please say if X what is prefered? for example to hold state or to be inherited.... – Ahmad Dec 03 '14 at 18:00
  • Please, fix your question so it says "member variables" instead of "global variables". You are creating huge misunderstanding the way your question is now. – Euphoric Dec 03 '14 at 18:31
  • @Ahmad Variables used throughout your class should generally be passed as constructor parameters and saved in your class. Otherwise, the parameters should be specific to the method and "forgotten" by your class when the method is finished if possible. – Neil Dec 04 '14 at 08:36

3 Answers3

3

The first is marginally better, but in general, you should try to avoid unnecessary side effects altogether.

Why? Modifying input parameters is vile. It is unexpected, it is difficult to test, it is difficult to reuse, it creates problems in concurrent environments...

Modifying a class is sometimes fine, that is what classes are for really... but it can be difficult to test, difficult to modify/extend, it creates problems in concurrent environments... And you usage here where Foo is just some placeholder to pass around in Method3 defeats the point of the class.

A better approach would be something like this:

Foo Method1(Foo x){ ... }
Foo Method2(Foo x){ ... }
Foo Method3(){
    Foo stuff = ...;
    stuff = Method1(stuff);
    stuff = Method2(stuff);
    // and return or just do stuff with stuff...
}
Telastyn
  • 108,850
  • 29
  • 239
  • 365
  • Thank you but you said it is difficult to reuse? but I think when the input and output is specified we better can use them isn't it? – Ahmad Dec 03 '14 at 18:47
  • @Ahmed - I'm not sure I understand. Neither of your examples specify the input and output for the functions. – Telastyn Dec 03 '14 at 18:58
  • Yes, they take inputs and receive a member variable as input however doesn't provide output, they could though. I have also a question about your answer, you say the better approach for providing input and output is what you wrote, or it is the best approach? – Ahmad Dec 03 '14 at 19:02
  • @Ahmad - if functions work against member variables, you need not explicitly pass them in. What is best depends on what the class needs to do. One approach may be best in one scenario, but worse in others. – Telastyn Dec 03 '14 at 19:06
  • I also got your point about the class, but I provided the methods to get input to be able to be reused for different variables, which I think might be a bad idea? – Ahmad Dec 03 '14 at 19:06
  • I got confused! you first admit the first approach, but it seems you count some disadvantages for both mentioned approaches? right? and you give your approach one which doesn't have those problems? – Ahmad Dec 03 '14 at 19:19
  • Yes, you are right, if they work against member variables, then passing them is less meaningful, I modified my question but now my question is what are really member variables? when they are used? – Ahmad Dec 03 '14 at 19:33
2

You're confusing global variables for member variables.

Modifying member variables in member functions is fine, when your function is meant to modify the class state. That's kind of why OOP came about in the first place: to logically group state and behavior. If it's getting hard to keep track of whats happening to your member variables, that's not a problem in using them, the problem is your class is too large.

If you are going to be passing variables around non-stop, you might as well not use any OOP features.

Another thing to consider, is that if modifying member variables was considered unwise, then what would be the point of a setter function?

Let's look at an example of a circular buffer, written in C, swiped from Wiki page:

/* Opaque buffer element type.  This would be defined by the application. */
typedef struct { int value; } ElemType;

/* Circular buffer object */
typedef struct {
    int         size;   /* maximum number of elements           */
    int         start;  /* index of oldest element              */
    int         end;    /* index at which to write new element  */
    ElemType   *elems;  /* vector of elements                   */
} CircularBuffer;

void cbInit(CircularBuffer *cb, int size) {
    cb->size  = size + 1; /* include empty elem */
    cb->start = 0;
    cb->end   = 0;
    cb->elems = calloc(cb->size, sizeof(ElemType));
}

void cbFree(CircularBuffer *cb) {
    free(cb->elems); /* OK if null */
}

int cbIsFull(CircularBuffer *cb) {
    return (cb->end + 1) % cb->size == cb->start;
}

int cbIsEmpty(CircularBuffer *cb) {
    return cb->end == cb->start;
}

/* Write an element, overwriting oldest element if buffer is full. App can
   choose to avoid the overwrite by checking cbIsFull(). */
void cbWrite(CircularBuffer *cb, ElemType *elem) {
    cb->elems[cb->end] = *elem;
    cb->end = (cb->end + 1) % cb->size;
    if (cb->end == cb->start)
        cb->start = (cb->start + 1) % cb->size; /* full, overwrite */
}

/* Read oldest element. App must ensure !cbIsEmpty() first. */
void cbRead(CircularBuffer *cb, ElemType *elem) {
    *elem = cb->elems[cb->start];
    cb->start = (cb->start + 1) % cb->size;
}

The C example does pass everything in via arguments, transported around via a struct. This is done because C doesn't offer any built-in OOP features, so that's what you have to do.

If we make that in C++, making everything in the struct a member variable and accessing it 'globally' is entirely appropriate:

class CircularBuffer
{
  private:
    int         size;   // maximum number of elements           
    int         start;  // index of oldest element              
    int         end;    // index at which to write new element  
    ElemType   *elems;  // vector of elements                   
  public:
    CircularBuffer(int cbSize) {
      size = cbSize + 1; // include empty elem
      start = 0;
      end = 0;
      elems = new ElemType[size];
    }

    ~CircularBuffer() {
      delete[] elems;
    }

    bool IsFull() {
      return (end+1) % size == start;      
    }

    bool IsEmpty() {
      return end == start;
    }

    void Write(ElemType *elem) {
      elems[end] = *elem;
      end = (end + 1) % size;
      if (end == start)
        start = (start + 1) % size; //full, overwrite
    }

    //Read oldest element. App must ensure !IsEmpty() first.
    void Read(ElemType *elem) {
        *elem = elems[start];
        start = (start + 1) % size;
    }
}

If you instead had methods that did something like this:

  void Write(ElemType *elem)
  {
    _Write(ElemType *elem, this->start, this->end, this->size);
  }

or worse, had to call the member like so:

  cb.Write(&elem,cb.start,cb.end,cb.size);

there would be much rage.

The point is that the variables start, end, size, and elems are part of the internal state of the class, and so those can be manipulated directly inside the classes methods. The user of the class can only manipulate them through the exposed functions, as part of Information Hiding. If you are just going to pass the variables around to all the member functions, like you're writing C, then you might as well use C properly instead of compiling it with a C++ compiler.

whatsisname
  • 27,463
  • 14
  • 73
  • 93
1

First case is hugely better. BUT only as long as the containing class is cohesive. Using first option when the class has many different responsibilities will introduce unwanted bindings which make understanding the flow of the behavior of the class worse than in second case.

Also, the first approach will have an advantage, when you have many different variables to work with and when different functions might take different variables. This would mean all functions would need to get all variables, even though only few are used by function itself and rest is just passed into other methods.

There is one example, where the case of member variables is end result of refactoring. It is when you have complex and long method, but refactoring it into individual method calls would introduce tons of method parameters being passed around. In this case, you turn the whole method into a single class and turn the method's variables into a classes's members. Then, you can easily divide the one huge method into smaller ones that are all contained inside the class and that don't have to pass around all the variables, because those are contained within the same class.

Euphoric
  • 36,735
  • 6
  • 78
  • 110