#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) {
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.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 std::ostream& Print(std::ostream&) const =0;
};
inline
std::ostream& operator<<(std::ostream& lhs, const IClock& rhs) {
return rhs.Print(lhs);
}
#endif
#include "Clock.h"
#include <iostream>
#include <iomanip>
std::ostream& 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();
return s;
}
#include "Counter.h"
void Counter::Count(int amount) {
while (amount-- > 0) {
if (++value_ == max_value_) {
value_ = 0;
if (next_counter_) {
next_counter_->Count(1);
}
}
}
}
void Counter::Reset() {
value_ = 0;
}
clock-2:
g++ -std=c++14 *.cpp -o $@
run: clock-2
./clock-2
clean:
-rm -f *.o a.out core clock-2
zip:
zip ../clock-2.zip *.txt *.cpp *.h
Adding an interface to the clock
================================
Adding an interface (class) `IClock` makes the design more
flexible. Among other things several different types of clocks
(with different counter arrangements) may implememt this
interface.
It is not shown in this solution, as simply moving all of the
former member functions of clock to the `IClock` interface is
disputables: It defeats at least one of the design goals, if is
to operate clocks with a different number of counters over that
interface.
To advance to the next step apply the following changes:
* Improve the design by avoiding the need to have a data member
`next_counter_` in the counters when it is not needed (ie. in
the last counter of the chain):
* implement one class `LimitCounter`
* handling the reset logic in its `Count` member function
* but does NOT allow to chain to a next counter stage;
* from that derive a class `OverflowCounter` which calls the
base class member function `LimitCounter::Count` for the
reset logic but once it detects when a reset takes place and
then forwards the carry.
* As there is always a next counter stage, instead of a pointer
a reference can (and should) be used to connect to it.
NOTE: For an easier start here are the skeletons of the two
classes:
class LimitCounter {
private:
int value_;
int max_value;
public:
LimitCounter(int max_value)
: value_{0}
, max_value_{max_value}
{}
...
};
class OverflowCounter : public LimitCounter {
private:
LimitCounter& next_counter_;
...
public:
OverflowCounter(int max_value, LimitCounter& next_counter)
: LimitCounter{max_value}
, next_counter{next_counter_}
{}
...
};
* The "hint" whether a reset took place, required should may
* EITHER be given by a `bool` return value of
`LimitCounter::Count` which then is tested
in `OverflowCounter::Count`
* OR by testing the base class counter value.
NOTE: For an easier start here are the skeletons how the two
`Count` member functions might be implemented.
// EITHER (first solution) that is the way to go:
bool LimitCounter::Count() {
...increment counter...
if ( ...test whether limit is reached, if so ...) {
... reset value to 0 ...
return true;
}
return false;
}
// then override (and reuse) the inherited implementation
bool OverflowCounter::Count() {
if (LimitCounter::Count()) {
next_counter_.Count();
return true;
}
return false;
}
// ALTERNATIVELY (second solution) that is the way to go:
void LimitCounter::Count() {
...increment counter...
if ( ...test whether limit is reached, if so ...) {
... reset value to 0 ...
}
}
// then override (and reuse) the inherited implementation
void OverflowCounter::Count() {
LimitCounter::Count();
if (LimitCounter::GetValue() == 0) {
// obviously a reset occured
next_counter_.Count();
}
}
* Do similar changes for the `LimitCounter::Reset`:
* simply set the counter value to `0`
* And to `OverflowCounter::Reset`:
* call the inherited implementation,
* then reset the chained counter.
=================================================================
Optional:
* Make sure you understand the design and especially why
* with three or more counters in the chain it is ESSENTIAL to
use late binding (= `virtual` member functions) for
`LimitCounter::Count` and `LimitCounter::Reset`,
* while with only two counters (ie. a single `OverflowCounter`
connected to a single `LimitCounter` it would work without.
* Furthermore, comparing the new designawith a base and a derived
class to the previous pointer-based solution with a single
class, which made a decision whether there is a next counter
in the chain in a conditional statement
...
if (next_counter_ != nullptr)
next_counter_.Count();
...
to WHERE is this test gone now? How does the equivalent branch
in the control-flow – that is still necessary(!) – happen?