继承和 2 种接口类型

Inheritance and 2 interface types

本文关键字:接口类型 继承      更新时间:2023-10-16

我有一个关于继承和设计用户界面的问题。

我有一个class KeyboardKey,它代表单个键盘键,例如Q,W,E,R,...等。

我有一个包含class Keyboardkey向量的class Keyboard。[重要!

我正在使用 SFML,因此每次从事件循环生成事件时,它都会发送到键盘类。然后,此类将该事件农场化到相应的键。

此外,我还有一个继承自KeyboardKeyclass SynthesizerKey。除了常规键的内容,例如"是否启用键","是否按下键",此类还包含用于处理生成正弦波音调的数据和函数。变量包括正弦波的振幅和当前相位。

我现在要创建一个class SynthesizerKeyboard.我正要从class Keyboard复制并粘贴所有代码到这个类中,但是这不是好的编程实践,因为代码是重复的!

我遇到的主要问题是SynthesizerKeyboard包含一个函数来生成要存储在缓冲区中的样本序列。为了生成样本,循环遍历每个KeyboardKey并检查它是否被按下。如果是,那么我们必须生成一个对应于该键音符/频率的样本。

但是,由于向量包含class KeyboardKey而不是class SynthesizerKey因此我没有正弦波的相位和振幅的变量作为向量元素的成员数据。

我想我可能不得不做所谓的"重构"[?],并将SynthesizerKey的"罪恶波"部分与KeyboardKey部分分开。换句话说,我放弃了SynthesizerKey类,分别有一个Synthesizer类和一个KeyboardKey类。然后,除了class KeyboardKeyboardKey的向量之外,我还class SynthesizerKeyboard

中有一个Synthesizer向量,我可以通过继承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();
}
}
};

合成器键

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

键盘键

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

现在要跳过键,但你应该考虑类似的东西。

首先定义一个执行所有常见任务的裸骨抽象,并包括用于专门填充抽象以执行其特定行为的类的钩子:

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中添加虚拟析构函数和 virtual 关键字来Sample,我们还可以制作一个

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