I am in the process of refactoring a rather large portion of spaghetti code. In a nutshell it is a big "God-like" class that branches into two different processes depending in some condition. Both processes are lengthy and have lots of duplicated code.
So my first effort has been to extract those two processes into their own classes and putting the common code in a parent they both inherit from.
It looks something like this:
public class ExportProcess
{
public ExportClass(IExportDataProvider dataProvider, IExporterFactory exporterFactory)
{
_dataProvider = dataProvider;
_exporterFactory = exporterFactory;
}
public void DoExport(SomeDataStructure someDataStructure)
{
_dataProvider.Load(someDataStructure.Id);
var exporter = _exporterFactory.Create(_dataProvider, someDataStructure);
exporter.Export();
}
}
I am an avid reader of Mark Seemann's blog and in this entry he explains that this code has a temporal coupling smell since it is necessary to call the Load method on the data provider before it is in a usable state.
Based on that, and since the object is being injected to the ones returned by the factory anyway, I am thinking of changing the factory to do this:
public IExporter Create(IExportDataProvider dataProvider, SomeDataStructure someDataStructure)
{
dataProvider.Load(someDataStructure.Id);
if(dataProvider.IsNewExport)
{
return new NewExportExporter(dataProvider, someDataStructure);
}
return new UpdateExportExporter(dataProvider, someDataStructure);
}
Because of the name "DataProvider" you probably guessed that the Load method is actually doing a database access.
Something tells me an object doing a database access inside the create method of an abstract factory is not a good design.
Are there any guidelines, best practices or something that say this is effectively a bad idea?
Thanks for your help.
Typically, a factory is used to resolve concrete types of a requested interface or abstract type, so you can decouple consumers from implementation. So usually a factory is just going to discover or specify the concrete type, help resolve dependencies, and instantiate the concrete type and return it. However, there's no hard or fast rule as to what it can or can't do, but it is sensible to give it enough access to only to resources that it needs to resolve and instantiate concrete types.
Another good use of a factory is to hide from consumers types dependencies that are not relevant to the consumer. For example, it seems IExportDataProvider
is only relevant internally, and can be abstracted away from consumers (such as ExportProcess
).
One code smell in your example, however, is how IExportDataProvider
is used. The way it currently seems to work, you get an instance of it once, but it's possible to change its state in subsequent usages (by calling Load
). This can lead to issues with concurrency and corrupted state. Since I don't know what that type does or how it's actually used by your IExporter
, it's hard to make a recommendation. In my example below, I make an adjustment so that we can assume that the provider is stateless, and instead Load
returns some sort of state object that the factory can use to resolve the concrete type of exporter, and then provide data to it. You can adjust that as you see fit. On the other hand, if the provider has to be stateful, you'll want to create an IExportDataProviderFactory
, use it in your exporter factory, and create a new instance of the provider from the factory for each call to exporter factory's Create
.
public interface IExporterFactory
{
IExporter Create(SomeDataStructure someData);
}
public class MyConcreteExporterFactory : IExporterFactory
{
public MyConcreteExporterFactory(IExportDataProvider provider)
{
if (provider == null) throw new ArgumentNullException();
Provider = provider;
}
public IExportDataProvider Provider { get; private set; }
public IExporter Create(SomeDataStructure someData)
{
var providerData = Provider.Load(someData.Id);
// do whatever. for example...
return providerData.IsNewExport ? new NewExportExporter(providerData, someData) : new UpdateExportExporter(providerData, someData);
}
}
And then consume:
public class ExportProcess
{
public ExportProcess(IExporterFactory exporterFactory)
{
if (exporterFactory == null) throw new ArgumentNullException();
_exporterFactory = factory;
}
private IExporterFactory _exporterFactory;
public void DoExport(SomeDataStructure someData)
{
var exporter = _exporterFactory.Create(someData);
// etc.
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With