Factories
At the very top level of the library -- just below the level which would be occupied by application-specific logic and main() -- there are factories to generate the resource objects required by the rest of the functionality from the inputs to the program.
A simple example is the PsalterFactory:
class PsalterFactory
{
public:
PsalterFactory(const IConfigSource& inConfig);
std::unique_ptr<IPsalter> create(const Log::ILogger &inLogger);
private:
std::string m_filename;
};
PsalterFactory::PsalterFactory(const IConfigSource &inConfig):
m_filename(inConfig.getConfigDir() + inConfig.getPsalmsFile())
{ }
std::unique_ptr<IPsalter> PsalterFactory::create(const Log::ILogger &inLogger)
{
std::unique_ptr<Psalter> rval = std::make_unique<Psalter>();
std::ifstream str(m_filename);
if (!str)
throw std::runtime_error("Could not open psalm file " + m_filename
+ " for parsing");
PsalmParser().parse(*rval, str, inLogger);
return rval;
}
This does three things as a separate object:
1) It hides the updateable aspect of the Psalter from the rest of the program (IPsalter is an immutable interface).
2) It encapsulates the connection between the configuration values and the Psalter as an object.
3) It simplifies logic at the call site when it is used.
Note that this is not an abstract factory; if an application needed a different implementation of IPsalter (as the Unit Tests do) it would have to create it independently.
A different model is that taken by the HymnSource management. Instead of returning a pointer to the interface, it implements the interface and interns the created object, delegating its operations. Otherwise it's broadly similar:
class FileHymnSource : public IHymnSource
{
public:
FileHymnSource(const IConfigSource &inConfig, const Log::ILogger &inLogger);
~FileHymnSource() override;
std::string getHymn(const std::string &inName,
const HymnSeasons inSeason) const override
{
return m_delegate->getHymn(inName, inSeason);
}
std::string getAnnueChriste(const std::string &inInterpolatedVerse,
const bool inIsPlural) const override
{
return m_delegate->getAnnueChriste(inInterpolatedVerse, inIsPlural);
}
private:
std::unique_ptr<IHymnSource> m_delegate;
};
FileHymnSource::FileHymnSource(const IConfigSource &inConfig,
const Log::ILogger &inLogger)
{
std::ifstream fstr;
std::string filename(inConfig.getConfigDir() + inConfig.getHymnsFile());
fstr.open(filename);
if (!fstr)
{
inLogger.log(Log::Severity::Error, "Could not open file " + filename + " for reading");
throw std::runtime_error("Could not open file " + filename + " for reading");
}
auto source = std::make_unique<HymnSource>();
HymnParser parser(inLogger, fstr, *source);
parser.parse();
m_delegate = std::move(source);
}
The DayRecordSource factory follows the factory model, but requires a IDateCalculator resource as well:
class DayRecordSourceFactory
{
public:
DayRecordSourceFactory(const IConfigSource &inConfig,
const IDateCalculator &inDateCalculator,
const unsigned int inDay,
const unsigned int inMonth);
std::unique_ptr<IDayRecordSource> create(const IPsalter &inPsalter);
private:
std::string m_filename;
Use m_use;
HymnSeasons m_season;
Days m_day;
};
DayRecordSourceFactory::DayRecordSourceFactory(
const IConfigSource &inConfig, const IDateCalculator &inDateCalculator,
const unsigned int inDay, const unsigned int inMonth):
m_filename(inConfig.getConfigDir() + inConfig.getHoursInfoFile()),
m_use(inConfig.getUse()),
m_season(inDateCalculator.getSeason(inDay, inMonth)),
m_day(inDateCalculator.getDay(inDay, inMonth))
{}
std::unique_ptr<IDayRecordSource>
DayRecordSourceFactory::create(const IPsalter &inPsalter)
{
std::ifstream str(m_filename);
if (!str)
throw std::runtime_error("Could not open hours info file " + m_filename
+ " for reading");
return std::make_unique<IndexedDayRecordSource>(str, inPsalter, m_use,
m_season, m_day);
}
The DateCalculator derives its inputs not from the configuration but from the command line:
class DateCalculatorFactory
{
public:
DateCalculatorFactory(const JSBUtil::ICommandLineOptions &inOptions);
std::unique_ptr<IDateCalculator> create();
private:
int m_year = 0;
std::string m_date;
};
DateCalculatorFactory::DateCalculatorFactory(
const JSBUtil::ICommandLineOptions &inOptions):
m_year(std::atoi(inOptions.getDoubleArg("y").c_str())),
m_date(inOptions.getDoubleArg("e"))
{}
std::unique_ptr<IDateCalculator> DateCalculatorFactory::create()
{
if (!m_date.empty())
return std::make_unique<DateCalculator>(m_date);
else if (m_year != 0)
{
static EasterTable theTable;
return std::make_unique<DateCalculator>(theTable.getEaster(m_year));
}
else
return std::make_unique<DateCalculator>();
}
The most interesting factory is that for generating the SaintsDaySet which is used in creating the Kalendar object. The sets vary depending on their configuration, so the main class not only implements the abstract interface:
class ISaintsDaySetFactory
{
public:
virtual ~ISaintsDaySetFactory();
virtual std::unique_ptr<ISaintsDaySet> create() const = 0;
};
class SaintsDaySetFactory : public ISaintsDaySetFactory
{
public:
SaintsDaySetFactory(const IDateCalculator &inCalculator,
const IConfigSource &inConfig,
const Log::ILogger &inLogger);
~SaintsDaySetFactory() override;
std::unique_ptr<ISaintsDaySet> create() const override
{
return m_delegate->create();
}
private:
std::unique_ptr<ISaintsDaySetFactory> m_delegate;
};
but it converts the configuration information into special implementations of the same interface, and delegates to them:
SaintsDaySetFactory::SaintsDaySetFactory(const IDateCalculator &inCalculator,
const IConfigSource &inConfig,
const Log::ILogger &inLogger)
{
if (inConfig.getUse() == Use::Anglican)
m_delegate = std::make_unique<SarumSaintsDaySetFactory>(inCalculator);
else if (auto s = inConfig.getKalendarFile(); !s.empty())
m_delegate = std::make_unique<ConfigurableRomanSaintsDaySetFactory>(
inCalculator, inConfig.getConfigDir() + s, inLogger);
else
m_delegate
= std::make_unique<SimpleRomanSaintsDaySetFactory>(inCalculator);
}
The three delegates are:
class SarumSaintsDaySetFactory : public ISaintsDaySetFactory
{
public:
SarumSaintsDaySetFactory(const IDateCalculator &inCalculator):
m_calculator(inCalculator)
{
}
~SarumSaintsDaySetFactory() override;
std::unique_ptr<ISaintsDaySet> create() const override;
private:
const IDateCalculator &m_calculator;
};
std::unique_ptr<ISaintsDaySet> SarumSaintsDaySetFactory::create() const {
return std::make_unique<BaseSaintsDaySet>(m_calculator);
}
This follows a decision to make the Sarum office implementation non-extensible in terms of additional days. Such days would either come from the older Roman Breviary or from new additions to the Calendar.
class SimpleRomanSaintsDaySetFactory : public ISaintsDaySetFactory
{
public:
SimpleRomanSaintsDaySetFactory(const IDateCalculator &inCalculator):
m_calculator(inCalculator)
{ }
~SimpleRomanSaintsDaySetFactory() override;
std::unique_ptr<ISaintsDaySet> create() const override;
private:
const IDateCalculator &m_calculator;
};
std::unique_ptr<ISaintsDaySet> SimpleRomanSaintsDaySetFactory::create() const {
auto rval = std::make_unique<ExtensibleSaintsDaySet>(m_calculator);
// Change Holy Name to Roman Date
std::string text(
R"(<FEASTS>
<REMOVE day="7" month="8"/>
<FEAST day="3" month="1" name="Holy Name of Jesus" grade="DOUBLE_SECOND_CLASS"/>
</FEASTS>
)");
Log::NullLogger theNullLogger;
std::istringstream str(text);
rval->extendFromStream(str, theNullLogger);
return std::move(rval);
}
class ConfigurableRomanSaintsDaySetFactory : public ISaintsDaySetFactory
{
public:
ConfigurableRomanSaintsDaySetFactory(const IDateCalculator &inCalculator,
const std::string &inFilename,
const Log::ILogger &inLogger):
m_calculator(inCalculator),
m_filename(inFilename), m_logger(inLogger)
{
}
~ConfigurableRomanSaintsDaySetFactory() override;
std::unique_ptr<ISaintsDaySet> create() const override;
private:
const IDateCalculator &m_calculator;
std::string m_filename;
const Log::ILogger &m_logger;
};
std::unique_ptr<ISaintsDaySet>
ConfigurableRomanSaintsDaySetFactory::create() const
{
auto rval = std::make_unique<ExtensibleSaintsDaySet>(m_calculator);
std::string text(
R"(<FEASTS>
<REMOVE day="7" month="8"/>
<FEAST day="3" month="1" name="Holy Name of Jesus" grade="DOUBLE_SECOND_CLASS"/>
</FEASTS>
)");
std::istringstream str(text);
rval->extendFromStream(str, m_logger);
return std::move(rval);
std::ifstream inFile(m_filename);
if (!inFile)
m_logger.log(Log::Severity::Error,
"Could not open file " + m_filename + " for reading");
else
{
rval->extendFromStream(str, m_logger);
}
return std::move(rval);
}
Finally, we have the factory for the formatter, which uses both the configuration file and the command line:
class OfficeFormatterFactory
{
public:
OfficeFormatterFactory(const IConfigSource &inConfig, const JSBUtil::ICommandLineOptions &inOptions);
std::unique_ptr<IEncapsulatedOfficeFormatter> create();
private:
std::string m_outputFileName;
int m_lineLength;
Use m_use;
};
namespace
{
std::ofstream theStream;
}
using namespace std::literals;
namespace BreviaryLib
{
OfficeFormatterFactory::OfficeFormatterFactory(const IConfigSource &inConfig,
const JSBUtil::ICommandLineOptions &inOptions):
m_lineLength(inConfig.getLineLength()),
m_use(inConfig.getUse()),
m_office(inOptions.getDoubleArg("O")),
m_outputFileName(inOptions.getDoubleArg("f")),
m_config(inConfig)
{
if (inOptions.isSingleArg("x"))
{
std::time_t c_now = std::time(nullptr);
std::tm tstruct;
localtime_r(&c_now, &tstruct);
if (tstruct.tm_hour < 8)
m_office = "LAUDS";
else if (tstruct.tm_hour < 9)
m_office = "PRIME";
else if (tstruct.tm_hour < 11)
m_office = "TERCE";
else if (tstruct.tm_hour < 14)
m_office = "SEXT";
else if (tstruct.tm_hour < 17)
m_office = "NONE";
else if (tstruct.tm_hour < 20)
m_office = "VESPERS";
else
m_office = "COMPLINE";
}
}
std::unique_ptr<IEncapsulatedOfficeFormatter> OfficeFormatterFactory::create()
{
if (m_office.empty())
{
if (m_outputFileName.empty())
return std::make_unique<TextEncapsulatedOfficeFormatter>(std::cout,
m_config);
else
{
theStream.open(m_outputFileName);
if (!theStream)
throw std::runtime_error("Could not open output file "
+ m_outputFileName + " for writing");
return std::make_unique<TextEncapsulatedOfficeFormatter>(theStream,
m_config);
}
}
else
{
if (m_outputFileName.empty())
return std::make_unique<SingleOfficeTextEncapsulatedOfficeFormatter>(
m_office, std::cout, m_config);
else
{
theStream.open(m_outputFileName);
if (!theStream)
throw std::runtime_error("Could not open output file "
+ m_outputFileName + " for writing");
return std::make_unique<SingleOfficeTextEncapsulatedOfficeFormatter>(
m_office, theStream, m_config);
}
}
}
}
Note that we use a static ofstream to avoid problems with local variable lifespans in the case of output to a file.
Comments
Post a Comment