Good practice to design a ABC(Abstract Base Class)

2019-03-31 06:58发布

问题:

In java, we can define different interfaces and then later we can implement multiple interface for a concrete class.

// Simulate Java Interface in C++
/*
interface IOne {
    void   MethodOne(int i);
    .... more functions
}

interface ITwo {
    double MethodTwo();
    ... more functions
}

class ABC implements IOne, ITwo {
    // implement MethodOne and MethodTwo
}
*/

In C++, generally speaking, we should avoid the usage of multiple inheritance, although multi-inheritance does have its edge on some situations.

class ABC {
public:
    virtual void   MethodOne(int /*i*/) = 0 {}
    virtual double MethodTwo() = 0 {}

    virtual ~ABC() = 0 {}

protected:
    ABC() {} // ONLY ABC or subclass can access it
};

Question1> Based on the design of ABC, should I improve any other things in order to make it a decent ABC?

Question2> Is it true that a good ABC should not contain member variables and instead variables should be kept in the subclasses?

Question3> As I indicated in the comments, what if ABC has to contain too many pure functions? Is there a better way?

回答1:

  1. Do not provide an implementation for pure virtual methods unless it is necessary.
  2. Do not make your destructor pure virtual.
  3. Do not make your constructor protected. You cannot create an instance of an abstract class.
  4. Better hide an implementation of constructor and destructor inside a source file not to pollute other object files.
  5. Make your interface non-copyable.

If this is an interface, better do not have any variables there. Otherwise it would be an abstract base class and not an interface.

Too many pure functions is OK unless you can do it with less pure functions.



回答2:

In C++, generally speaking, we should avoid the usage of multiple inheritance

Like any other language feature, you should use multiple inheritance wherever it is appropriate. Interfaces are generally considered an appropriate use of multiple inheritance (see, for example, COM).

The constructor of ABC needs not be protected--it cannot be constructed directly because it is abstract.

The ABC destructor should not be declared as pure virtual (it should be declared as virtual, of course). You should not require derived classes to implement a user-declared constructor if they do not need one.

An interface should not have any state, and thus should not have any member variables, because an interface only defines how something is to be used, not how it is to be implemented.

ABC should never have too many member functions; it should have exactly the number that are required. If there are too many, you should obviously remove the ones that are not used or not needed, or refactor the interface into several more specific interfaces.



回答3:

Based on the design of ABC, should I improve any other things in order to make it a decent ABC?

You've got a couple of syntax errors. For some reason, you're not allowed to put a definition of a pure virtual function inside a class definition; and in any case, you almost certainly don't want to define them in the ABC. So the declarations would usually be:

virtual void MethodOne(int /*i*/) = 0;   // ";" not "{}" - just a declaration

There's not really any point in making the destructor pure, although it should be virtual (or, in some cases, non-virtual and protected - but it's safest to make it virtual).

virtual ~ABC() {}  // no "= 0"

There's no need for the protected constructor - the fact that it is abstract already prevents instantiation except as a base class.

Is it true that a good ABC should not contain member variables and instead variables should be kept in the subclasses?

Usually, yes. That gives a clean separation between interface and implementation.

As I indicated in the comments, what if ABC has to contain too many pure functions? Is there a better way?

The interface should be as complex as it needs to be, and no more. There are only "too many" functions if some are unnecessary; in which case, get rid of them. If the interface looks too complicated, it may be trying to do more than one thing; in that case, you should be able to break it up into smaller interfaces, each with a single purpose.



回答4:

First: why should we avoid multiple inheritance in C++? I've never seen a largish application which didn't use it extensively. Inheriting from multiple interfaces is a good example of where it is used.

Note that Java's interface is broken—as soon as you want to use programming by contract, you're stuck with using abstract classes, and they don't allow multiple inheritance. In C++, however, it's easy:

class One : boost::noncopyable
{
    virtual void doFunctionOne( int i ) = 0;
public:
    virtual ~One() {}
    void functionOne( int i )
    {
        //  assert pre-conditions...
        doFunctionOne( i );
        //  assert post-conditions...
    }
};

class Two : boost::noncopyable
{
    virtual double doFunctionTwo() = 0;
public:
    virtual ~Two() {}
    double functionTwo()
    {
        //  assert pre-conditions...
        double results = doFunctionTwo();
        //  assert post-conditions...
        return results;
    }
};

class ImplementsOneAndTwo : public One, public Two
{
    virtual void doFunctionOne( int i );
    virtual double doFunctionTwo();
public:
};

Alternatively, you could have a compound interface:

class OneAndTwo : public One, public Two
{
};

class ImplementsOneAndTwo : public OneAndTwo
{
    virtual void doFunctionOne( int i );
    virtual double doFunctionTwo();
public:
};

and inherit from it, which ever makes the most sense.

This is the more or less standard idiom; in cases where there cannot conceivably be any pre- or post-conditions in the interface (typically call inversion), the virtual functions may be public, but in general, they will be private, so that you can enforce the pre- and post-conditions.

Finally, note that in a lot of cases (especially if the class represents a value), you will just implement it directly, without the interface. Unlike Java, you don't need a separate interface to maintain the implementation in a different file from the class definition—that's the way C++ works by default (with the class definition in a header, but the implementation code in a source file).