I l@ve RuBoard |
![]() ![]() |
8.5 GeneralizationLet's enumerate the elements involved in our discussion of object factories. This gives us the intellectual workout necessary for putting together a generic object factory.
The generic factory will orchestrate these elements to provide a well-defined interface, as well as defaults for the most used cases. It seems that each of the notions just enumerated will transform into a template parameter of a Factory template class. There's only one exception: The concrete product doesn't have to be known to the factory. Had this been the case, we'd have different Factory types for each concrete product we're adding, and we are trying to keep Factory insulated from the concrete types. We want only different Factory types for different abstract products. This being said, let's write down what we've grabbed so far:
template
<
class AbstractProduct,
typename IdentifierType,
typename ProductCreator
>
class Factory
{
public:
bool Register(const IdentifierType& id, ProductCreator creator)
{
return associations_.insert(
AssocMap::value_type(id, creator)).second;
}
bool Unregister(const IdentifierType& id)
{
return associations_.erase(id) == 1;
}
AbstractProduct* CreateObject(const IdentifierType& id)
{
typename AssocMap::const_iterator i =
associations_.find(id);
if (i != associations_.end())
{
return (i->second)();
}
handle error
}
private:
typedef std::map<IdentifierType, AbstractProduct>
AssocMap;
AssocMap associations_;
};
The only thing left out is error handling. If we didn't find a creator registered with the factory, should we throw an exception? Return a null pointer? Terminate the program? Dynamically load some library, register it on the fly, and retry the operation? The actual decision depends very much on the concrete situation; any of these actions makes sense in some cases. Our generic factory should let the user customize it to do any of these actions and should provide a reasonable default behavior. Therefore, the error handling code should be pulled out of the CreateObject member function into a separate FactoryError policy (see Chapter 1). This policy defines only one function, OnUnknownType, and Factory gives that function a fair chance (and enough information) to make any sensible decision. The policy defined by FactoryError is very simple. FactoryError prescribes a template of two parameters: IdentifierType and AbstractProduct. If FactoryErrorImpl is an implementation of FactoryError, then the following expression must apply: FactoryErrorImpl<IdentifierType, AbstractProduct> factoryErrorImpl; IdentifierType id; AbstractProduct* pProduct = factoryErrorImpl.OnUnknownType(id); Factory uses FactoryErrorImpl as a last-resort solution: If CreateObject cannot find the association in its internal map, it uses FactoryErrorImpl<IdentifierType, Abstract-Product>::OnUnknownType for fetching a pointer to the abstract product. If OnUnknownType throws an exception, the exception propagates out of Factory. Otherwise, CreateObject simply returns whatever OnUnknownType returned. Let's code these additions and changes (shown in bold): template < class AbstractProduct, typename IdentifierType, typename ProductCreator, template<typename, class> class FactoryErrorPolicy > class Factory : public FactoryErrorPolicy<IdentifierType, AbstractProduct> { public: AbstractProduct* CreateObject(const IdentifierType& id) { typename AssocMap::const_iterator i = associations_.find(id); if (i != associations_.end()) { return (i->second)(); } return OnUnknownType(id); } private: ... rest of functions and data as above ... }; The default implementation of FactoryError throws an exception. This exception's class is best made distinct from all other types so that client code can detect it separately and make appropriate decisions. Also, the class should inherit one of the standard exception classes so that the client can catch all kinds of errors with one catch block. DefaultFactoryError defines a nested exception class (called Exception)[4] that inherits std::exception.
template <class IdentifierType, class ProductType> class DefaultFactoryError { public: class Exception : public std::exception { public: Exception(const IdentifierType& unknownId) : unknownId_(unknownId) { } virtual const char* what() { return "Unknown object type passed to Factory."; } const IdentifierType GetId() { return unknownId_; }; private: IdentifierType unknownId_; }; protected: StaticProductType* OnUnknownType(const IdentifierType& id) { throw Exception(id); } }; Other, more advanced implementations of FactoryError can look up the type identifier and return a pointer to a valid object, return a null pointer (if the use of exceptions is undesirable), throw some exception object, or terminate the program. You can tweak the behavior by defining new FactoryError implementations and specifying them as the fourth argument of Factory. |
I l@ve RuBoard |
![]() ![]() |