简体   繁体   中英

polymorphism and encapsulation of classes

I'm trying to take advantage of the polymorphism in c++, but I'm from a c world, and I think what I've done could be done more cleverly in a OOP way.

I have 2 classes that has exactly the same public attributes, and I want to "hide" that there exists 2 different implementations. Such that I can have a single class where I can use the member functions as If i were accessing the specific class.

An very simple implementation of what I'm trying to accomplish is below:

#include <iostream>

class subber{
private:
  int id;
public:
  int doStuff(int a,int b) {return a-b;};
};


class adder{
private:
  int id;
public:
  int doStuff(int a, int b) {return a+b;};
};


class wrapper{
private:
  int type_m;
  adder cls1;
  subber cls2;
public:
  wrapper(int type) {type_m=type;};//constructor
  int doStuff(int a, int b) {if(type_m==0) return cls1.doStuff(a,b); else return cls2.doStuff(a,b);};
};


int main(){
  wrapper class1(0);
  std::cout <<class1.doStuff(1,3) <<std::endl;
  wrapper class2(1);
  std::cout <<class2.doStuff(1,3) <<std::endl;
  return 0;
}

I have 2 classes called "subber" and "adder" which both have a member function called doStuff, which will either subtract of add 2 numbers.

This I wrap up in a class "wrapper", which has both "adder" and "subber" as private variables, and a doStuff public member function. And given which value I instantiate my "wrapper" class with, my "wrapper" class will simply relay the "doStuff" to the correct class.

This code does of cause work, but I would like to avoid instatiating both "subber" and "adder" in my wrapper class, since I will only need of them in each of my "wrapper" classes.

Thanks

There are many ways to do it. Through a Factory for example.

But to keep it simple - make a base abstract class that defines the interface, and derive your classes from it to implement the functionality. Then you only need to make the distinction once, when you create the class, after that you don't care, you just call the interface functions.

your code would look something like that.

class DoStuffer
{
    public:
        virtual int doStuff(int, int)=0;
        virtual ~DoStuffer(){}; // Because Tony insists:-) See the comments
}

class subber: public DoStuffer{
public:
  virtual int doStuff(int a,int b) {return a-b;};
};


class adder: public DoStuffer{
public:
  virtual int doStuff(int a, int b) {return a+b;};
};

int main(){
  DoStuffer *class1 = new adder();
  DoStuffer *class2 = new subber();
  std::cout <<class1->doStuff(1,3) <<std::endl;
  std::cout <<class2->doStuff(1,3) <<std::endl;
  delete class1; // don't forget these:-)
  delete class2;
  return 0;
}

The classic run-time polymorphic approach is:

struct Operation
{
    virtual ~Operation() { }   // guideline: if there are any virtual functions,
                               //            provide virtual destructor
    virtual int doStuff(int, int) const;
};

struct Subber : Operation
{
    int doStuff(int a, int b) const { return a - b; }
};

struct Adder : Operation
{
    int doStuff(int a, int b) const { return a + b; }
};

enum  Operations { Add, Subtract };

struct Operation* op_factory(Operations op)
{
    if (op == Add) return new Adder;
    if (op == Subtract) return new Subber;
    throw std::runtime_error("unsupported op");
}

int main()
{
    Operation* p1 = op_factory(Add);
    std::cout << p1->doStuff(1,3) <<std::endl; 
    Operation* p2 = op_factory(Subtract);
    std::cout << p2->doStuff(1,3) <<std::endl;
    delete p1;
    delete p2;
}

From the Standard 5.3.5/5 "In the first alternative (delete object), if the static type of the operand is different from its dynamic type, the static type shall be a base class of the operand's dynamic type and the static type shall have a virtual destructor or the behavior is undefined." , which is why you must use the virtual keyword on the base class destructor.

It's noteworthy that in your example the type of operation to perform was communicated to the wrapper class using a function argument of 0 or 1... this is what suggests you want run-time polymorphism. For example, if the 0 or 1 value was based on a command line argument, file content, keyboard input etc., then the factory method above can pass a corresponding Add or Subtract value and receive an appropriately-behaving object derived from Operation. This concept of creating an instance of a run-time polymorphic type based on run-time values is known as a factory.

If you really only need compile-time polymorphism, you can do some interesting things with templates such as:

template <class Operation>
void output(int a, int b)
{
    std::cout << Operation::doStuff(a, b) << std::endl;
    std::cout << Operation::doStuff(a * 10, b * 10) << std::endl;
    std::cout << Operation::doStuff(a * 100, b * 100) << std::endl;
}

int main()
{
    output<adder>(1, 3);
    output<subber>(1, 3);
}

FWIW, your approach is probably slightly faster than the virtual function approach (as it can potentially do more inlining), but not as clean, extensible, maintainable or scalable.

This is one of the more idiomatic ways to use the C++ class system to accomplish what you want. Both adder and subber publicly inherit from wrapper , which is now an abstract base class. The doStuff method is now a (pure) virtual function. And instead of being a simple instance of wrapper , the "encapsulated" object is now a reference to a wrapper .

#include <iostream>

class wrapper {
public:
  virtual int doStuff(int a, int b) = 0;
};

class subber : public wrapper {
public:
  virtual int doStuff(int a,int b) {return a - b;}
};

class adder : public wrapper {
public:
  virtual int doStuff(int a, int b) {return a + b;}
};

int main(){
  // actual objects
  adder impl1;
  subber impl2;

  // in real code, the wrapper references would probably be function arguments
  wrapper& class1 = impl1;
  std::cout << class1.doStuff(1,3) << std::endl;
  wrapper& class2 = impl2;
  std::cout << class2.doStuff(1,3) << std::endl;
  return 0;
}

(Not using any factory pattern in this example, since it's not obvious that it's needed or what the question is about.)

Exactly what was last said.

Make a base class, and have a virtual function |doStuff| in it.

Then you can derive any number of classes out from it, all have to implement the above virtual function, in whatever way they want to.

Then you can just do the following

BaseClass *object1 = new DerivedClass1();
BaseClass *object2 = new DerivedClass2();
..

You can even do

object1 = object2;

And then they point to the same object (ie an object of type |DerivedClass2|)

But remember, when you do objectn->doStuff() , the function that will be executed will be what the pointer points to at run-time, and not at compile time.

ie if I do object1->doStuff() DerivedClass2's doStuff will be called because we already did `object1 = object2;

You may want to Google and read about

Polymorphism/ Run-time Polymorphism Virtual Functions in C++

You can read Factory Method, which is something that is known as a Design Pattern, but later in life.

Thanks

I think what you're looking for is virtual functions. If you declare a function virtual in your base class, you can do things like make a vector containing multiple objects derived from your base class, but when you call on a particular object it will execute it's own method.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM