to split or not to split that is the question

Hi all,

I have a doubt on the proper way to design a class (which we'll call MyClass)

MyClass has a member function called "run()"
However, the definition of "run()" contains an "if(mode)" instruction where "mode" is an attribute of the class.
depending on the value of "mode", either the short or the full version of the code is executed:

1
2
3
4
5
6
7
8
void MyClass::run(void)
{
  doStuff(a,b)
  //
  if(mode) {
    doMoreStuff(a,b,c,d,e)
  }
}


This was a simple example ; the actual case is a bit more complicated:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void MyClass::run(void)
{
  doStuffA(a,b)
  if(mode) {
    doMoreStuffA(a,b,c,d,e)
  }
  doStuffB(a,b)
  if(mode) {
    doMoreStuffB(a,b,c,d,e)
  }
  doStuffC(a,b)
  if(mode) {
    doMoreStuffC(a,b,c,d,e)
  }
}


The class declaration looks something like:

1
2
3
4
5
6
7
8
9
class MyClass
{
public:
    MyClass(al,be);
	MyClass(al,be,ga,del,ep);
private:
    bool mode;
    SomeClass & a,b,c,d,e;
};


A problem here is that we only need c,d,e if mode==true, yet these are references and need to be initialised even if mode==false. A work around is to do:

1
2
3
4
5
6
7
8
9
10
11
MyClass::MyClass
	(SomeClass & al
    ,SomeClass & be)
      :a( al               )
      ,b( be               )
      ,c( *new SomeClass() ) //dummy initialisation
      ,d( *new SomeClass() ) //dummy initialisation
      ,e( *new SomeClass() ) //dummy initialisation
{
    //do nothing
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
MyClass::MyClass
	(SomeClass & al
    ,SomeClass & be
    ,SomeClass & ga
    ,SomeClass & del
    ,SomeClass & ep
      :mode ( true )
      :a    ( al   )
      ,b    ( be   )
      ,c    ( ga   )
      ,d    ( del  )
      ,e    ( ep   )
{
    //do nothing
}


But I don't really like this way of proceeding: there should not be a need for dummy initialisations…

An alternative would be to rewrite that code in order to split MyClass into two classes, one for each mode. However, that will leave me with two classes having a very similar definition for run(). In other words, it is some kind of a duplicate, which perhaps might bring me maintenance problems ?

What would you do ?

Cheers
c, d and e as non-owning pointers (instead of references)?
Something along these lines:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
struct A ; struct B ; struct C ;

struct my_class
{
    my_class( A& aa, B& bb ) : a(aa), b(bb) {}
    my_class( A& aa, B& bb, C& cc ) : a(aa), b(bb), c_( std::addressof(cc) ) {}

    void run()
    {
        do_stuff( a, b ) ;
        if( mode() )
        {
            do_more_stuff_a( a, b, c() ) ;
            do_more_stuff_b( a, b, c() ) ;
            do_more_stuff_c( a, b, c() ) ;
        }
    }

    private:
        A& a ;
        B& b ;

        // optional C
        C* c_ = nullptr ; // non-owning pointer
        inline C& c() { if( c_ == nullptr ) throw std::runtime_error("nullptr") ; return *c_ ; }
        inline const C& c() const { if( c_ == nullptr ) throw std::runtime_error("nullptr") ; return *c_ ; }

        inline bool mode() const { return c_ != nullptr ; }

        void do_stuff( A&, B& ) ;
        void do_more_stuff_a( A&, B&, C& ) ;
        void do_more_stuff_b( A&, B&, C& ) ;
        void do_more_stuff_c( A&, B&, C& ) ;
};


If the classes are object-oriented types, accessed via an interface, the null-object pattern could be an alternative.
https://en.wikipedia.org/wiki/Null_Object_pattern
@JLBorges thanks for your answer !

I think it'd be nicer for me to have a,b and c treated "similarly" inside the function definition (by "similarly" I mean I don't want to use the "*" operator at places and not other). Hence, I think I'll opt for the null-object approach.

Thanks for the help !

Cheers
Based on what you provided, I would go ahead and create 2 classes. The base class would be without mode, and the derived class would be with mode. The code would be something like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
MyClass
{
public:
	MyClass(SomeClass& a1,  SomeClass& be) : a(a1), b(be) { }
	void run() { myDoStuffA(); myDoStuffB(); myDoStuffC(); }

protected:
	virtual void myDoStuffA() { doStuffA(a, b); }
	virtual void myDoStuffB() { doStuffB(a, b); }
	virtual void myDoStuffC() { doStuffC(a, b); }

private:
	SomeClass & a, b;
};

MyModeClass : public MyClass
{
public:
	MyModeClass(SomeClass& a1, SomeClass& be, SomeClass ga,
			SomeClass& del, SomeClass& ep) :
		MyClass(a1, be), c(ga), d(del), e(ep) { }

	virtual void myDoStuffA() { MyClass::myDoStuffA();
					doMoreStuffA(a, b, c, d, e); }
	virtual void myDoStuffB() { MyClass::myDoStuffB();
					doMoreStuffB(a, b, c, d, e); }
	virtual void myDoStuffC() { MyClass::myDoStuffC();
					doMoreStuffC(a, b, c, d, e); }
private:
	SomeClass & c, d, e;
};


If there is no reason to initialize c, d or e if there is no mode, that seems to me to indicate a new class.
Last edited on
@doug4

Interesting solution you are suggesting here: cancels the need for an if instruction... I need to see if that can be implemented easily in my (exact) case
Topic archived. No new replies allowed.