Meta-Factories

A meta-factory is a factory that creates or encapsulates other factories. When implementing a Logging Layer in a system, this pattern can also be very useful to reduce maintenance. The idea is that we create a single factory that will return an object that has all of the factories you need to create a complex class that contains additional classes. It can further be extended to return an object that contains all the factories needed to execute a service call.

One of the interesting effects of creating the meta-factory is that we are essentially creating a dependency graph showing every class we are using inside of the service call. This in turn, allows us to make a Logging Layer meta-factory that also ensures that every object being created has the corresponding Logging L:ayer version.

An important aspect to consider for these meta-factories is how to handle DAOs. You may decide to pass in the external connections as constructor arguments, and have the meta-factory create the DAOs. Or, you may create the DAOs, and pass them into the meta-factory constructor. The latter approach allows for more flexibility, since you can add features to the DAOs. For example, in some cases you may want to add caching to a DAO that is not usually there.

As an example, we are going to create a meta factory for a service that uses two database connections, a service call, three functional classes, and a class that is a return type. Both database connections and the service call will be encapsulated in DAOs, which in turn will be used by other classes. Below is pseudocode for the constructors for the various classes. using Connectors; namespace ServiceName { namespace Daos { Database1(DatabaseConnection) Database2(DatabaseConnection) ExternalService1(ServiceConnection) } namespace Entities { Class1(Database1) Class2(Database2, ReturnedTypeFactory) Class3(ExternalService1, ReturnedTypeFactory) ReturnedType() } namespace Factories { Class1Factory(Database1) Class2Factory(Database2, ReturnedTypeFactory) Class3Factory(ExternalService1, ReturnedTypeFactory) ReturnedTypeFactory() } }

Next, we look at the Meta-Factory that will be used to construct all instances of Class1, Class2, and Class3. Please note closely how the various constructors interact to generate all of the factories. using Connectors; namespace ServiceName.Factories { public class MetaFactory { private Database1 _database1; private Database2 _database2; private Service1 _service1; private Class1Factory _factory1; private Class2Factory _factory2; private Class3Factory _factory3; private ReturnedTypeFactory _returned; // this only takes in the connections // it constructs the DAOs, and the ReturnedTypeFactory and passes // them into an intermediate constructor public MetaFactory(DatabaseConnection database1, DatabaseConnection database2, ServiceConnection service1) : this(new Database1(database1), new Database2(database2), new Service1(service1), new ReturnedTypeFactory()) {} // this takes in the DAOs, and ReturnedTypeFactory, and constructs // all of the the other factories. Note how the ReturnedTypeFactory is used in // three different places. // this constructor is private since its entire purpose is to serve as // an intermediate construction step. private MetaFactory(Database1 database1, Database2 database2, Service1 service1, ReturnedTypeFactory returned) : this(database1, database2, service1, new Class1Factory(database1), new Class2Factory(database2, returned), new Class3Factory(service1, returned), returned) {} // this constructor takes one each of the factories and DAOs. It was made // protected so that the Traceable version may use it. protected MetaFactory(Database1 database1, Database2 database2, Service1 service1, Class1Factory factory1, Class2Factory factory2, Class3Factory factory3, ReturnedTypeFactory returned) { // this appears that we don't need several of the factories stored here // but there are advantages. For one, you can implement a function // that outputs all of the connection information used, or have the // meta-factory create a central structure that takes care of such needs _database1 = database1; _database2 = database2; _service1 = service1; _factory1 = factory1; _factory2 = factory2; _factory3 = factory3; _returned = returned; } public Class1 ConstructClass1() { return _factory1.Construct(); } public Class2 ConstructClass2() { return _factory2.Construct(); } public Class3 ConstructClass3() { return _factory3.Construct(); } } }

Next we look at the constructors of the Logging versions of all the classes. They are the same as the Functional Layer, but they all take in the ILogger interface as well. Also note that their arguments, aside from the ILogger, are from the Functional Layer. using Connectors; using SD = ServiceName.Daos; using SE = ServiceName.Entities; using SF = ServiceName.Factories; namespace LoggingServiceName { namespace Daos { Database1(DatabaseConnection, ILogger) : SD.Database1 Database2(DatabaseConnection, ILogger) : SD.Database2 ExternalService1(ServiceConnection, ILogger) : SD.ExternalService1 } namespace Entities { Class1(SD.Database1, ILogger) : SE.Class1 Class2(SD.Database2, SF.ReturnedTypeFactory, ILogger) : SE.Class2 Class3(SD/ExternalService1, SF.ReturnedTypeFactory, ILogger) : SE.Class3 ReturnedType(ILogger) : SE.ReturnedType } namespace Factories { Class1Factory(SD.Database1) : SF.Class1Factory Class2Factory(SD.Database2, SF.ReturnedTypeFactory) : SF.Class2Factory Class3Factory(SD.ExternalService1, SF.ReturnedTypeFactory) : SF.Class3Factory ReturnedTypeFactory() : SF.ReturnedTypeFactory } }

Now we implement the Logging Meta-Factory. We have a public constructor that takes in the same external connections plus an ILogger. In turn, it calls an intermediate constructor that constructs some of the shared factories and DAOs. And then it calls the protected constructor... in the Functional class. All there is the to the Logging MetaFactory are the constructors. using Connectors; using SD = ServiceName.Daos; using SE = ServiceName.Entities; using SF = ServiceName.Factories; using TD = TraceableServiceName.Daos; using TE = TraceableServiceName.Entities; using TF = TraceableServiceName.Factories; namespace LoggingServiceName.Factories { public class MetaFactory : ServiceName.Factories.MetaFactory { public MetaFactory(DatabaseConnection database1, DatabaseConnection database2, ServiceConnection service1, ILogger logger) : this(new TD.Database1(database1, logger), new TD.Database2(database2, logger), new TD.Service1(service1, logger), new TF.ReturnedTypeFactory(logger), logger) {} private MetaFactory(SD.Database1 database1, SD.Database2 database2, SD.Service1 service1, SF.ReturnedTypeFactory returned, ILogger logger) : base(database1, database2, service1, new TF.Class1Factory(database1, logger), new TF.Class2Factory(database2, returned, logger), new TF.Class3Factory(service1, returned, logger), returned) {} } }

Design Note: this example was able to only have constructors in the Logging Layer since the relationship between all of the classes was unidirectional. If some of the classes have circular references, then you may need to create helper functions to help with their creation. And of course, the Logging Meta-Factory may have to override these helper functions, in which case, make sure any necessary factories are protected instead of private.

Next: Conclusion

Copyright © 2017-2018 Adin H. Baber, all rights reserved.