#include "Clock.h"
extern void appl(IClock&);
int main()
{
std::cout.setf(std::ios::boolalpha);
Clock c{};
appl(c);
}
#include <iostream>
#define SHOW(...)\
(void)(std::cout << __FILE__ << ':' << __LINE__ << '\t'\
<< #__VA_ARGS__ << " --> " << (__VA_ARGS__)\
<< std::endl)
#include "IClock.h"
void appl(IClock& c) {
SHOW(c);
c.TickUp(); SHOW(c);
c.TickUp(); SHOW(c);
c.TickUp(); SHOW(c);
c.Set(0, 0, 58); SHOW(c);
c.TickUp(); SHOW(c);
c.TickUp(); SHOW(c);
c.TickUp(); SHOW(c);
c.Set(0, 59, 58); SHOW(c);
c.TickUp(); SHOW(c);
c.TickUp(); SHOW(c);
c.TickUp(60); SHOW(c);
c.TickUp(600); SHOW(c);
}
#ifndef ICLOCK_H
#define ICLOCK_H
#include <iosfwd>
class IClock {
public:
virtual ~IClock() =default;
virtual void Set(int, int, int) =0;
virtual void TickUp(int = 1) =0;
virtual void Print(std::ostream&) const =0;
};
inline
std::ostream& operator<<(std::ostream& lhs, const IClock& rhs) {
rhs.Print(lhs);
return lhs;
}
#endif
#ifndef CLOCK_H
#define CLOCK_H
#include <iosfwd>
#include <iostream>
#include "IClock.h"
#include "LimitCounter.h"
#include "OverflowCounter.h"
class Clock : public IClock {
LimitCounter hours_;
OverflowCounter minutes_;
OverflowCounter seconds_;
public:
Clock(int hours = 0, int minutes = 0, int seconds = 0)
: hours_{24}
, minutes_{60, hours_}
, seconds_{60, minutes_}
{
Set(hours, minutes, seconds);
}
Clock(const Clock&) =delete; // copy c'tor
Clock& operator=(const Clock&) =delete; // copy assignment
Clock(Clock&&) =delete; // move c'tor
Clock& operator=(Clock&&) =delete; // move assignment
virtual void Set(int hours, int minutes, int seconds) override {
hours_.SetValue(hours);
minutes_.SetValue(minutes);
seconds_.SetValue(seconds);
}
virtual void TickUp(int seconds) override { seconds_.Count(seconds); }
virtual void Print(std::ostream&) const override;
};
#endif
#include "Clock.h"
#include <iostream>
#include <iomanip>
void Clock::Print(std::ostream& s) const {
std::ostream os{s.rdbuf()};
os.fill('0');
os << std::setw(2) << hours_.GetValue() << ':'
<< std::setw(2) << minutes_.GetValue() << ':'
<< std::setw(2) << seconds_.GetValue();
}
#ifndef COUNTER_H
#define COUNTER_H
#include <limits>
class LimitCounter {
private:
int value_{};
const int max_value_{};
virtual void HasOverflowed() {}
virtual void HasResetted() {}
public:
LimitCounter(int max_value = std::numeric_limits<int>::max())
: value_{0}
, max_value_{max_value}
{}
auto SetValue(int value) { value_ = value; }
auto GetValue() const { return value_; }
void Count(int amount);
void Count();
void Reset();
};
#endif
#include "LimitCounter.h"
void LimitCounter::Count(int amount) {
while (amount-- > 0) {
Count();
}
}
void LimitCounter::Count() {
if (++value_ >= max_value_) {
value_ = 0;
HasOverflowed();
}
}
void LimitCounter::Reset() {
value_ = 0;
HasResetted();
}
#ifndef OVERFLOW_COUNTER_H
#define OVERFLOW_COUNTER_H
#include "LimitCounter.h"
class OverflowCounter : public LimitCounter {
LimitCounter& next_counter_;
public:
OverflowCounter(const OverflowCounter&) =delete;
OverflowCounter& operator=(const OverflowCounter&) =delete;
OverflowCounter(OverflowCounter&&) =delete;
OverflowCounter& operator=(OverflowCounter&&) =delete;
OverflowCounter(int max_value, LimitCounter& next_counter)
: LimitCounter{max_value}
, next_counter_{next_counter}
{}
virtual void HasOverflowed() override;
virtual void HasResetted() override;
};
#endif
#include "OverflowCounter.h"
void OverflowCounter::HasOverflowed() {
next_counter_.Count();
}
void OverflowCounter::HasResetted() {
next_counter_.Reset();
}
all: main run
run: main
./main
main: *.cpp *.h
g++ -std=c++14 *.cpp -o $@
clean:
-rm -f *.o a.out core main
zip: clean
zip ../clock-04.zip *.txt *.cpp *.h Makefile
.PHONY: all run clean zip
Apply NVI-Idiom ("Non-Virtual Interface")
-----------------------------------------
This solution not only shows "NVI" to the minimal amount
necessary to solve forwarding "overflows" and "resets" but
already provided more extension points … just "in case".
* PRO-s:
* More flexibility / adaptability
* CON-s:
* Potentially calls to many subroutines that do nothing but
return immediately if many of the provided extension points
aren't actually used.
To advance to the next step apply the following changes:
* Add an interface `ICounter` to class `LimitCounter`,
with the member functions `Count` and `Reset`.
* in the interface these need to be pure `virtual` but
* to keep with the NVI-idiom in `LimitCounter` these should
be `override final`
If you are good with the above, start right away. Otherwise,
if you need a bit more of guidance, read on.
Adding an Interface to class `LimitCounter`means you first add
a new file defining the interface. It can look a bit like this
(of course, this needs to be wrapped in the usual include guards
and the new file must be included at the appropriate places):
// ----------------------------------
class ICounter {
virtual ~ICounter() =default;
virtual void Count() =0;
virtual void Reset() =0;
};
// ----------------------------------
* Having `LimitCounter` implement that interface means as little
as changing it like below. The current content can stay as it
was unless you want to indicate which member functions implement
some interface function (using `override`) and that your design
follows the NVI idiom (using `final`) at the appropriate places.
// ----------------------------------
class LimitCounter : public ICounter {
...
};
// ----------------------------------
One purpose of that interface is to use it for the chain counter
reference, so the actual counter coupled to lower stages gets more
freedom what it can be.
* This requires TWO changes to `OverflowCounter`:
* Once as type for `next_counter_`
* Second as consructor argument type!
=================================================================
Optional/Advanced
Discuss based on the LSP – ie. each DERIVED class (when accessed
via reference or pointer) – may be used in place of any (direct
or indirect) of its BASE classes (including interfaces:
* Which classes can currently be used in place of which other
classes (or interfacesa
NOTE: The following question may be "theoretically" answereds
based on the LSP only, but the MINIMAL way to IMPLEMENT it
requires the use of private inheritance combined with
virtual base classes, so its defintely NOT for beginners.
* If `OverflowCounter` should only usable in place of `ICounter`
but NOT in place or `LimitCounter`, with which changes could
this be achieved?