Частное наследование и не виртуальные интерфейсы
Так что некоторое время я интересовался D, и некоторое время назад возился с ним. Я начал смотреть на это снова, и мне действительно нравится то, чего он пытается достичь, но у меня есть сомнения по поводу одного из моих любимых вариантов дизайна C++... не виртуальных интерфейсов.
Что мне нравится в этом проекте, так это то, что он позволяет выполнять предварительную и последующую проверку условий, ведение журналов и управление ресурсами на "вершине" иерархии наследования. Это позволяет разработчику определить все общие функциональные возможности группы связанных классов и разбить настраиваемые части класса на очень маленькие функции. Это также уменьшает количество функций, которые необходимо записать в подкласс. Кроме того, поскольку виртуальные точки расширения являются частными, они не загрязняют интерфейс и не позволяют пользователям напрямую вызывать специфичные для реализации функции (что действительно является ключевым).
Есть ли способ добиться этого в D?
Пример на C++ (непроверенный, не скомпилированный... только для иллюстрации).
class Radio{
public:
Radio( std::string id, Station defaultStation, RxChip chip)
:defaultStation(defaultStation),
id(id),
chip(chip){
}
void turnOn() {
log.trace("Radio turned on: id:[%s]", id.c_str());
doEnableRx();
doPostEnable();
setToStation(defaultStation);
}
void turnOff(){
log.trace("Radio turned off: id:[%s]", id.c_str());
doDisableRx();
doPowerOff();
}
void tune(){
log.trace("Tuning");
findAllStations();
}
void setToStation(Station target){
logStationChange(target);
doSetRxChipPassFilter(target);
}
void setChip(RxChip chip) {
rxChip = chip;
}
RxChip getChip() {
return rxChip;
}
private:
// doesn't start with "do" as this is considered a "normal" virtual function.
virtual void findAllStations(){
chip.setFrequency(chip.getLowFreq());
setChipToNextTunedPoint();
Station stat( chip.getFrequency(), tunedStations.size() );
tunedStations.push_back(stat);
}
virtual bool setChipToNextTunedPoint() {
if(chip.isTuned()) {
while( isTuned && chip.getFrequency() < chip.getHighFreq() )
chip.incrementFreq();
}
while( !chip.isTuned() && chip.getFrequency() < chip.getHighFreq() )
chip.incrementFreq();
return chip.isTuned();
}
// "do" functions are considered mandatory extension points for sub-classes
virtual void doEnableRx() = 0;
virtual void doPostEnable() = 0;
virtual void doDisableRx() = 0;
virtual void doPowerOff() = 0;
virtual void doSetRxChipPassFilter(Station target) = 0
{
//default implementation but it must be specified for use by sub-class.
chip.setFrequency(target.getLowFreq());
while( !chip.isTuned() && chip.getFrequency() < station.getHighFreq() ) {
chip.incrementFreq();
}
}
Station defaultStation;
std::vector<Station> tunedStations;
RxChip chip;
}
1 ответ
Конечно. Для не виртуальной функции-члена, либо сделайте это final
(так что компилятор может оптимизировать его виртуальность) или шаблонизировать его, и тогда он гарантированно будет не виртуальным, поскольку функции шаблона никогда не являются виртуальными. Чтобы шаблонизировать функцию без параметров шаблона, просто дайте ей пустой список параметров шаблона. например
void setChip(RxChip chip) {...}
становится
void setChip()(RxChip chip) {...}
А для виртуальной функции просто сделайте это protected
, В настоящий момент, private
а также package
никогда не являются виртуальными, поэтому, если вы хотите, чтобы функция была виртуальной, вы должны сделать это либо public
или же protected
и делая это protected
, это не доступно общедоступным API. Вы не можете пойти полноправным и сделать это private
как и в C++, но, возможно, это все равно ничего вам не даст, так как переопределяющая функция все еще может вызываться классом, в котором она находится. Итак, все делает это private
делает так, чтобы вы не могли вызывать версию базового класса (которая обычно является чисто виртуальной / абстрактной).
Тем не менее, я хотел бы отметить, что если все, что вам нужно, это контракты, D's in
а также out
блоки поддерживают полиморфизм. Таким образом, вам может даже не понадобиться NVI. В этот момент у вас будет просто функция базового класса in
а также out
блоки с тем, что в контрактах и вне контрактов вы хотите, и они будут вызываться при вызове производной функции. Это просто работает для утверждений, которые вы хотите для предварительных и постусловий, но в некоторых случаях устраняет необходимость в NVI.