2015-02-07 2 views
2

Прошу прощения, это, наверное, глупый вопрос. Я, очевидно, неправильно понимаю что-то фундаментальное в отношении объектно-ориентированного программирования. Я привык к C, и теперь я пытаюсь использовать C++.C++ объект со своим методом?

У меня есть несколько кнопок в классе под названием Button. Каждая кнопка делает что-то другое. То, что я хочу писать что-то вроде этого:

Button button1; 
Button button2; 
... 
void button1::onClick() { 
    ... 
} 
void button2::onClick() { 
    ... 
} 

Но это не работает («кнопка 1 не является классом, имен или перечисление» - да, я знаю!). Я знаю, что я мог бы просто сделать отдельный класс для каждой кнопки:

class button1_class : public Button { 
public: 
    void onclick() { 
     ... 
    } 
} button1; 
class button2_class : public Button { 
    ... 
} 

Но мне это кажется "неправильным сделать класс, когда я знаю, что он будет иметь только один элемент.

Я использую AGUI, библиотека GUI для Allegro 5.

EDIT

Спасибо за ответы. Хотя они все полезны и (я думаю) все действительные ответы, никто на самом деле не сказал «нет, у вас нет объекта с его собственным уникальным методом, потому что ...»

Так, например, если object1 имеет тип ObjectClass то объекту 1 не разрешается иметь метод (функция-член), который является уникальным для object1, но скорее имеет только методы, которые определены как часть ObjectClass. Это правильно? Прошу прощения, я не включил свой фактический прецедент. Я был более заинтересован в том, чтобы просто окунуться в ООП, чтобы я мог сделать это самостоятельно самостоятельно.

EDIT2 Глядя на ответы более подробно, я полагаю, что это возможно с лямбда-выражениями, это просто не так, как я себе представлял. Еще раз спасибо

+0

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

+0

В чем вопрос? – Walter

ответ

3

Если кнопки имеют различные функциональные возможности, лучше всего сделать, это создать BaseButton класс, в котором Вы отмечаете onclick() в virtual (или сделать его чисто виртуальным, что сделает BaseButton абстрактный класс), а затем вывести каждый другую кнопку от BaseButton, чтобы переопределить onclick() в каждом производном классе. Затем вам нужно использовать кнопки с помощью ссылки или указателя на BaseButton, таким образом вы достигнете так называемого «полиморфного поведения».

Например:

class BaseButton 
{ 
    virtual void onclick() {/*implement here or declare pure virtual*/} 
}; 

class RedButton: public BaseButton /* overrides only onclick */ 
{ 
    void onclick() override { /*specific implementation for Red Buttons */} 
}; 

class ShinyRedButton: public RedButton /* overrides only onclick */ 
{ 
    void onclick() override { /*specific implementation for Shiny Red Buttons */} 

}; 

затем использовать его как (C++ 14 смарт-указатели)

std::unique_ptr<BaseButton> bb = new ShinyRedButton; 
bb->onclick(); // will pick up the "right" ShinyRedButton::onclick()` function 
+0

Я не согласен с тем, что здесь есть новый тип для каждой кнопки. Если они не являются категориями кнопок, которые имеют жестко связанное поведение и будут часто использоваться повторно. Это зависит от использования. –

+0

@LightnessRacesinOrbit Я думаю, что это зависит от точного сценария. Из вопроса OPs было не совсем ясно: «Каждая кнопка делает что-то другое». Если только 'onclick()' отличается, тогда да, вы правы, вы должны передать функциональность лямбда. – vsoftco

+0

Да, как я сказал, это зависит. Следовательно, нет «-1» :) –

7

Естественный C++ способ это сделать, как описано vsoftco с виртуалов и наследования.

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

class Button { 
    function<void()> f; 
public: 
    Button(function<void()> mf) : f(mf) {} 
    void onClick() { f(); } 
}; 

Этот вариант вашего класса использует объект function (считайте его как своего рода указатель функции, но гораздо более гибким в использовании).

Вы можете использовать его с lambda-functions как в этом примере:

int main(int ac, char**av) 
{ 
    Button button1([&]() { cout << "Hello 1!\n"; }); 
    Button button2 ([]() { cout << "Hello 2!\n"; }); 

    button1.onClick(); 
    button2.onClick(); 
} 
+0

хороший способ сделать это! +1 – vsoftco

+0

Хороший ответ, я собирался опубликовать ту же идею. Но почему захват все по ссылке в lambda 'button1'? – 5gon12eder

1

Использование зОго :: функция является ключевой здесь. У вас будет прослушивание виртуального вызова и возможное распределение памяти, если ваша вызываемая функция (лямбда, функция, член) велика. Это позволяет удовлетворить ваши требования одного типа, выполняющего разные обратные вызовы, не определяя наследование класса. Кроме того, использование единообразной инициализации делает очень удобным создание класса Button с лямбдой без ручного создания конструктора.

Живой пример: http://coliru.stacked-crooked.com/a/f9007c3f103f3ffe

#include <functional> 
#include <vector> 
using namespace std; 

struct Button 
{ 
    function<void()> OnClick; 
}; 

int main() 
{ 
    vector<Button> buttons = 
    { 
     {[] { printf("Button0::OnClick()\n"); }}, 
     {[] { printf("Button1::OnClick()\n"); }}, 
     {[] { printf("Button2::OnClick()\n"); }}, 
    }; 

    for(auto&& button : buttons) 
    button.OnClick(); 
} 
+0

Нет никакой разницы между «вручную», создающим конструктор с списком инициализации члена и вашим подходом. С точки зрения генерации кода они делают то же самое (ваш код также генерирует неявный конструктор по умолчанию). – vsoftco

+0

Это точно точка. Тот же результат, меньше кода. Зачем делать больше работы, когда вы можете заставить компилятор сделать это за вас? Прежде чем обсуждать, требуется больше контекста проблемы, если требуется ограничение генерации неявно определенных конструкторов/операторов. – rparolin

+0

, в общем, кнопки немного сложнее, и я убежден, что инкапсуляция почти всегда является хорошей идеей (тем более, что вы можете сделать некоторую предварительную обработку параметров, переданных конструктору). Для этого конкретного примера да, я согласен, что ваша схема работает отлично. – vsoftco

1

Ваш Agui library поддерживает систему сигнализации, с функцией члена addActionListener.

Это позволяет получить класс от agui::ActionListener для выполнения конкретной задачи, предназначенную для одной или нескольких кнопок:

class SimpleActionListener : public agui::ActionListener 
{ 
public: 
    virtual void actionPerformed(const agui::ActionEvent &evt) 
    { 
     std::cout << "Button pushed" << std::endl; 
    } 
}; 

Объект выше, может быть присоединен к «прессу» действиям кнопки с:

SimpleActionListener simpleAL; 
button1.addActionListener(&simpleAL); 
2

Вы правы в этом, если каждая кнопка принципиально одинакова, но для этого привязаны разные обработчики событий, реализация нового типа для каждого из них не совсем правильная.

Вместо этого ваш тип Button будет иметь функцию-член, которая позволяет пользователям «прикреплять» обработчик события и функцию-член, чтобы вызвать его.

class Button 
{ 
public: 
    Button() 
     : onClickHandler() 
    {} 

    void setOnClickHandler(std::function<void()> callback) 
    { 
     onClickHandler = callback; 
    } 

    friend class UI; 

private: 
    void onClick() 
    { 
     onClickHandler(); 
    } 

    std::function<void()> onClickHandler; 
}; 

Тогда ваш пользователь делает:

void foo() 
{ 
    std::cout << "Some buttons do this!\n"; 
} 

Button btn; 
btn.setOnClickHandler(foo); 

И внутренности вашей программы будут созданы такие вещи, что ваш оконный менеджер (выше я предполагал, что это какой-то класс называется UI) вызывает btn.onClick() для вас, который, так как вы «привязаны» foo, в конечном итоге вызывают foo.

(В современных C++ вы, вероятно, сделать использование функций лямбды, чтобы привести в порядок это, но выше является простого примера, чтобы продемонстрировать общую дизайнерскую идею.)

Таким образом, вы можете прикрепить разные обработчики для разных Button экземпляров, но сам интерфейс Button является стабильным.

Это похоже на то, как, например, вы манипулируете DOM в JavaScript.

+0

Это тоже хорошо! – Christophe

+0

Я пользователь :) Библиотека GUI, которую я использую, имеет систему обработчиков событий, как указал Дрю Дорманн, используя класс ActionListener, который имеет виртуальный метод actionPerformed. Но тогда я бы все же определял отдельный класс ActionListener для каждой отдельной кнопки. Я предпочел бы иметь обработчик в объекте, а не в отдельном объекте, не в последнюю очередь потому, что мне кажется, что мне нужно будет также вызвать метод onclick. Но метода getActionListener нет, поэтому я не знаю, какой слушатель должен вызвать, учитывая кнопку. благодаря – corfizz

3

Вы можете сделать это разными способами.

  1. Использование Button класса, где объекты кнопки есть указатель на методы, которые вызываются onClick.В C вы могли бы сделать это с помощью обратного вызова, и вы также можете сделать это таким образом в C++:

    class Button { 
        using funType = void(void); 
    public: 
        Button(funType* callback) : function(callback) { } 
        void onClick() { function(); } 
    private: 
        funType* function; 
    }; 
    

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

    Button red([] { std::cout << "Red button\n"; }); 
    Button green(&green_button_function); 
    
  2. Создание различных Button объектов с различными onClick методами на лету. У C++ есть механизм для создания так называемых шаблонов:

    template <class Fun> 
    class Button { 
    public: 
        Button(Fun f) : functor(f) { } 
        void onClick() { functor(); } 
    private: 
        Fun functor; 
    }; 
    
    template <class Fun> 
    Button<Fun> make_button(Fun f) { return Button<Fun>(f); } 
    

    У меня отсутствуют данные, например, ссылки здесь. Затем можно использовать Button класс с обратными вызовами, а также лямбдами следующим образом:

    auto green = make_button([] { std::cout << "Green button pressed!\n"; }); 
    auto red = make_button(&red_button_function); 
    

    Вы должны использовать auto с помощью этого метода, так как в противном случае вы должны указать тип функциональности вручную , что невозможно, например для лямбда-объектов.

  3. Использование полиморфизма, как показано vsoftco, где вы создаете отдельные классы для каждой функциональности Button. Или вы можете создать абстрактный класс ButtonAction, к которому относится ссылка Button. Затем вы выполняете различные функции в разных классах, но оставайтесь с одним классом Button. Это известно как strategy pattern:

    class ButtonAction { 
    public: 
        virtual void onClick() = 0; 
    }; 
    
    class Button { 
    public: 
        Button(std::unique_ptr<ButtonAction> action) : 
         action_(std::move(action)) {} 
        void onClick() { action_->onClick(); } 
    private: 
        std::unique_ptr<ButtonAction> action_; 
    }; 
    
    class RedButtonAction : public ButtonAction { 
        void onClick() override { red(); } 
    }; 
    class GreenButtonAction : public ButtonAction { 
        void onClick() override { green(); } 
    }; 
    

    С помощью этого метода требует построения Button с от ButtonActionunique_ptr сек

    Button red(std::unique_ptr<ButtonAction>(new RedButtonAction)); 
    Button green(std::unique_ptr<ButtonAction>(new GreenButtonAction)); 
    
Смежные вопросы