полиморфизм и инкапсуляция классов - PullRequest
1 голос
/ 07 июня 2011

Я пытаюсь воспользоваться преимуществом полиморфизма в c ++, но я из мира c, и я думаю, что то, что я сделал, можно было бы сделать более умным способом ООП.

У меня есть 2 класса, которые имеют одинаковые публичные атрибуты, и я хочу «скрыть», что существует 2 разных реализации. Так что у меня может быть один класс, в котором я могу использовать функции-члены, как если бы я обращался к определенному классу.

Ниже приведена очень простая реализация того, что я пытаюсь выполнить:

#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;
}

У меня есть 2 класса с именами «subber» и «adder», которые оба имеют функцию-член с именем doStuff, которая либо вычтет из сложения 2 числа.

Это я обертываю в класс "обертка", который имеет как "сумматор", так и "суббер" как частные переменные, и публичную функцию-член doStuff. И учитывая, с каким значением я создаю экземпляр своего класса «обертки», мой класс «обертки» просто передаст «doStuff» правильному классу.

Этот код работает как причина, но я хотел бы избежать наложения обоих "subber" и "adder" в моем классе-обертке, поскольку они понадобятся мне только в каждом из моих классов-оберток.

Спасибо

Ответы [ 5 ]

3 голосов
/ 07 июня 2011

Есть много способов сделать это.Например, через Factory .

Но для простоты - создайте базовый абстрактный класс, который определяет интерфейс, и извлекайте из него ваши классы для реализации функциональности.Тогда вам нужно провести различие только один раз, когда вы создаете класс, после чего вам все равно, вы просто вызываете функции интерфейса.

ваш код будет выглядеть примерно так.

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;
}
1 голос
/ 07 июня 2011

Именно то, что было сказано последним.

Создайте базовый класс и создайте виртуальную функцию | doStuff |в нем.

Затем вы можете извлечь из него любое количество классов, все должны реализовать вышеупомянутую виртуальную функцию любым способом, каким они захотят.

Затем вы можете просто сделать следующее

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

Вы можете даже сделать

object1 = object2;

И тогда они указывают на один и тот же объект (т.е. объект типа | DerivedClass2 |)

Но помните, когдаесли вы выполните objectn->doStuff(), функция, которая будет выполнена, будет такой, на которую указывает указатель во время выполнения, а не во время компиляции.

, т.е. если я выполню object1->doStuff() doStuff DerivedClass2 будет вызван, потому что мыуже сделал `object1 = object2;

Вы можете захотеть в Google и прочитать о

Полиморфизм / Виртуальные функции полиморфизма во время выполнения в C ++

Вы можете прочитать Factory Method, которыйэто то, что известно как шаблон проектирования, но позже в жизни.

Спасибо

1 голос
/ 07 июня 2011

Это один из самых идиоматических способов использования системы классов C ++ для достижения желаемого.И adder, и subber публично наследуют от wrapper, который теперь является абстрактным базовым классом.Метод doStuff теперь является (чистой) виртуальной функцией.И вместо того, чтобы быть простым экземпляром wrapper, «инкапсулированный» объект теперь является ссылкой на 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;
}

(в этом примере не используется никакой фабричный шаблон, так как это не очевидночто это нужно или о чем вопрос.)

1 голос
/ 07 июня 2011

Классический полиморфный подход во время выполнения:

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;
}

Из стандарта 5.3.5 / 5 "В первом варианте (удаление объекта), если статический тип операнда отличается от его динамического типа, статический тип должен быть базовым классом динамического операнда Тип и статический тип должны иметь виртуальный деструктор или поведение не определено. ", поэтому вы должны использовать ключевое слово virtual в деструкторе базового класса.

Примечательно, что в вашем примере тип выполняемой операции был передан классу-оболочке с использованием аргумента функции 0 или 1 ... это то, что предполагает, что вам нужен полиморфизм во время выполнения. Например, если значение 0 или 1 основано на аргументе командной строки, содержимом файла, вводе с клавиатуры и т. Д., То описанный выше фабричный метод может передать соответствующее значение «Добавить» или «Вычесть» и получить объект с соответствующим поведением, полученный из операции. Эта концепция создания экземпляра полиморфного типа во время выполнения на основе значений времени выполнения называется фабрикой.

Если вам действительно нужен только полиморфизм во время компиляции, вы можете сделать некоторые интересные вещи с помощью таких шаблонов, как:

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, ваш подход, вероятно, немного быстрее, чем подход виртуальной функции (поскольку он потенциально может сделать больше встраивания), но не такой чистый, расширяемый, обслуживаемый или масштабируемый.

0 голосов
/ 07 июня 2011

Я думаю, что вы ищете виртуальные функции. Если вы объявляете функцию виртуальной в вашем базовом классе, вы можете делать такие вещи, как создание вектора, содержащего несколько объектов, производных от вашего базового класса, но когда вы вызываете определенный объект, он выполняет свой собственный метод.

...