2016-06-14 69 views
1

我有一個關於繼承和設計用戶界面的問題。繼承和2接口類型

我有一個class KeyboardKey其中表示單獨的鍵盤的鍵,如Q,W,E,R,...等

我有一個class Keyboard其中包含的class Keyboardkey一個矢量。 [重要!]

我正在使用SFML,所以每次從事件循環中生成事件時,都會將它發送到鍵盤類。然後這個類將這個事件傳遞給相應的鍵。

另外,我有一個class SynthesizerKey繼承自KeyboardKey。除了常規關鍵的東西,例如「是關鍵啓用」,「關鍵是關鍵」之外,該類還包含用於處理產生正弦波音的數據和函數。變量包括正弦波的幅度和電流相位。

我現在要創建一個class SynthesizerKeyboard。我正要將class Keyboard中的所有代碼複製並粘貼到這個類中,但這不是很好的編程習慣,因爲代碼是重複的!

我遇到的主要問題是SynthesizerKeyboard包含一個函數,用於生成要存儲在緩衝區中的樣本序列。爲了生成樣本,循環遍歷每個KeyboardKey並檢查它是否被按下。如果是,那麼我們必須生成一個對應於該鍵音符/頻率的樣本。

然而,由於載體含有class KeyboardKeyclass SynthesizerKey我沒有對作爲向量元素的構件數據罪波的相位和振幅的變量。

我想我可能不得不做所謂的「重構」[?],並將SynthesizerKey的「正弦波」部分與KeyboardKey部分分開。換句話說,我分開了SynthesizerKey類,並且有一個Synthesizer類和一個KeyboardKey類。然後,我在class SynthesizerKeyboard中有一個Synthesizer的矢量,此外還有KeyboardKey的矢量class Keyboard,我可以通過繼承訪問SynthesizerKeyboard

但是這不太優雅。有另一種方法嗎?

下面是一些代碼,可以幫助讀者更詳細地瞭解問題。

SynthesizerKeyboard

class SynthesizerKeyboard : public Keyboard 
{ 

public: 

    SynthesizerKeyboard(const sf::Font& sf_font) 
     : Keyboard(sf_font) 
    { 
    } 

    double Sample() const 
    { 
     for(std::vector<KeyboardKey>::iterator it = m_keyboardkey.begin() 
      it != m_keyboardkey.end(); ++ it) 
     { 
      if(it->IsKeyPressed()) 
      { 
       it->Sample(); 
      } 
     } 
    } 

    void GenerateBufferSamples(std::vector<sf::Int16> buffer) 
    { 
     for(std::size_t i = 0; i < buffer.size(); ++ i) 
     { 
      buffer[i] = Sample(); 
     } 
    } 

}; 

SynthesizerKey

class SynthesizerKey : public KeyboardKey 
{ 

protected: 

    AbstractOscillator *m_abstractoscillator; 

public: 

    double Sample() const 
    { 
     return m_abstractoscillator->Sample(); 
    } 

}; 

鍵盤

class Keyboard 
{ 

protected: 

    std::vector<KeyboardKey> m_keyboardkey; 

public: 

    Keyboard(const sf::Font& sf_font) 

    void Draw(sf::RenderWindow& window) 

    void Event(const sf::Event& event) 
    { 
     for(std::vector<KeyboardKey>::iterator it = m_keyboardkey.begin(); 
      it != m_keyboardkey.end(); ++ it) 
     { 
      (*it).Event(event); 
     } 
    } 

    bool IsKeyPressed(const sf::Keyboard::Key& sf_key) 
    { 
     for(std::vector<KeyboardKey>::iterator it = m_keyboardkey.begin(); 
      it != m_keyboardkey.end(); ++ it) 
     { 
      if((*it).Key() == sf_key) 
      { 
       return (*it).IsKeyPressed(); 
      } 
     } 
    } 

}; 

KeyboardKey

class KeyboardKey 
{ 

protected: 

    KeyState m_keystate; 
    sf::Color m_pressed_color; 
    sf::Color m_release_color; 
    sf::Text m_sf_text; 
    sf::Keyboard::Key m_sf_keyboard_key; 
    sf::RectangleShape m_sf_rectangle; 

public: 

    KeyboardKey(const sf::Keyboard::Key& sf_keyboard_key, const std::string& text, const sf::Font& sf_font, 
       const double position_x, const double position_y) 

    void Draw(sf::RenderWindow& window) 

    void Event(const sf::Event& event) 

    bool IsKeyPressed() 

}; 
+0

您可以劃分的代碼你擔心複製在'AbstractKeyboard'中完成了由'AbstractKeyboard'的繼承者實現的純虛擬專用函數,例如,當需要'Moog'特定的行爲時,將從'AbstractKeyboard'調用。'AbstractKeyboard'還包含'std :: vector >',這樣'KeyboardKey'或者適當的子類就可以被包含而沒有內存管理問題。 – user4581301

+0

@ user4581301我不確定我是否完全理解了您的建議,可以向我進一步解釋這個問題,或者可以添加一個建議的答案,或者添加一個示例? – user3728501

回答

0

將跳過鍵現在,但你應該考慮爲他們類似的東西。

首先定義一個光禿禿的骨頭抽象執行所有的常見任務,包括專門的抽象類填寫執行自己的特定行爲掛鉤:

class AbstractKeyboard 
{ 
protected: 
    std::vector<std::unique_ptr<KeyboardKey>> m_keyboardkey; 
    void Draw(); 
    void Event() 
    { 
     for(auto &key: m_keyboardkey) 
     { 
      key->Event(); 
     } 
    } 

    bool IsKeyPressed(const sf::Keyboard::Key& sf_key) 
    { 
     for(auto &key: m_keyboardkey) 
     { 
      if(key->isKey(sf_key)) 
      { 
       return key->IsKeyPressed(); 
      } 
     } 
     return false; // need this to handle the no match case, otherwise undefined behaviour 
    } 
    void doStuff() 
    { 
     // generic keyboard stuff goes here 
     doSpecificStuff(); 
    } 
    virtual void doSpecificStuff() = 0; 
public: 
    AbstractKeyboard(const sf::Font& sf_font); 
    virtual ~AbstractKeyboard(); 
}; 

所有的鍵盤有鑰匙,所以鑰匙矢量在這裏。請注意,我們已經從鑰匙的vector變成了智能鑰匙的vector。現在我們可以擁有任何繼承基本鍵的鍵,例如合成器鍵,智能指針消除了處理指針的常見內存問題。

這裏的大賣點是doStuff函數。它做的東西。什麼是你。當它完成所有鍵盤必須做的基本工作時,它會調用doSpecificStuff,即每個繼承者必須填寫的函數,即使它沒有任何作用。 doSpecificStuff可以做任何不同的繼承者,添加額外的行爲,並且通常使合成器不僅僅是普通的鍵盤。

這裏是基本的鍵盤:

class Keyboard:public AbstractKeyboard 
{ 
protected: 
    void doSpecificStuff() 
    { 
     // Do keyboard Stuff, if there is any specific stuff to do 
    } 
public: 
    Keyboard(const sf::Font& sf_font): AbstractKeyboard(sf_font) 
    { 

    } 

}; 

它不會做什麼特別的事情,但它可以,但把特殊的代碼爲doSpecificStuff

該合成器爲知道它是合成器的民謠(SampleGenerateBufferSamples)添加了一些功能,並且實現了doSpecificStuff做合成器的東西。

class SynthesizerKeyboard : public AbstractKeyboard 
{ 
protected: 
    void doSpecificStuff() 
    { 
     // I do specific stuff! Yay me! 
    } 

public: 

    SynthesizerKeyboard(const sf::Font& sf_font): AbstractKeyboard(sf_font) 
    { 

    } 

    // leaving these as an example of how to get a SynthesizerKey out of m_keyboardkey 
    double Sample() const 
    { 
     // just going to sum because I don't know what's supposed to happen in here 
     double sum = 0.0; 
     for(auto &key: m_keyboardkey) 
     { 
      if(key->IsKeyPressed()) 
      { 
       if(SynthesizerKey* skey = dynamic_cast<SynthesizerKey*>(key.get())) 
       { 
        sum += skey->Sample(); 
       } 
       else 
       { 
        // like freak out, man. 
       } 
      } 
     } 
     return sum; 
    } 

    void GenerateBufferSamples(std::vector<sf::Int16> buffer) 
    { 
     for(sf::Int16 & val: buffer) 
     { 
      val = Sample(); 
     } 
    } 

}; 

由於合成器使用合成密鑰,Sample包含如何的指針轉動到正規鑰匙插入一個合成關鍵和陷阱,則以錯誤類型的鍵爲m_keyboardkey

配置錯誤的示例通過增加一個虛析構函數和SynthesizerKeyboard虛擬關鍵字Sample,我們也可以做一個

class MoogSynthesizer: public SynthesizerKeyboard 
{ 
public: 
    double Sample() const override 
    { 
     // I do Moog sampling!    
    } 
}