There is a central problem with the article you linked to which means it does not give a good example of how an abstract factory can be used to make an application more extensible:
The problem is that both enum Manufacturers
and class PhoneTypeChecker.CheckProducts
have to be aware of all types of manufacturers. This almost entirely defeats the purpose of the abstract factory pattern (unless you interpret CheckProducts()
as an "abstract-factory-factory" method - in which case welcome to Enterprise Java hell.
...but if we make a few tweaks to the composition, the advantages become clear.
First, remove enum MANUFACTURER
(which shouldn't be capitalized anyway) and CheckProducts
and replace it with an IManufacturerRepository
.
- Important note: If you're actually writing real-code that interfaces with an ORM, then do not make your own Repository types because your ORM provides the repository for you. This is especially true with Entity Framework. Especially avoid the "generic repository" anti-pattern.
The IManufacturerRepository
should only have a single implementation (or a separate one for testing/mocking).
public interface IManufacturerRepository
{
List<IManufacturer> GetManufacturers();
}
public interface IManufacturer
{
String Name { get; }
IPhoneFactory PhoneFactory { get; } // <-- this is the abstract factory
}
Then the PhoneTypeChecker
can receive the IManufacturer
via constructor argument:
- I note that this example is silly because there's no meaningful instance state mutation inside
PhoneTypeChecker
nor does PhoneTypeChecker
represent any kind of operation represented by an abstract type or interface - this would be better done by making CheckProducts
a static
method that accepts IManufacturer
as an parameter).
- In the original article, the
CheckProducts
method does mutate PhoneTypeChecker
's state (by setting factory
, sam
, htc
) but this is a bad design - because it's really just initializing itself, which should be done inside the constructor - but I digress.
Anyway:
public class PhoneTypeChecker
{
private readonly IManufacturer mfg;
public PhoneTypeChecker( IManufacturer mfg )
{
this.mfg = mfg ?? throw new ArgumentNullExceptio(nameof(mfg));
}
public void CheckProducts()
{
Console.WriteLine( this.mfg.Name + ":\nSmart Phone: " +
this.mfg.PhoneFactory.GetSmart().Name() + "\nDumb Phone: " + this.mfg.PhoneFactory.GetDumb().Name() );
}
}
Then the Main
method would be redone as:
static void Main(string[] args)
{
IManufacturerRepository repo = new ActualIManufacturerRepository();
List<IManufacturer> mfgs = repo.GetManufacturers();
PhoneTypeChecker checker = new PhoneTypeChecker( mfgs.Single( m => m.Name == "Samsung" ) );
checker.CheckProducts();
Console.ReadLine();
checker = new PhoneTypeChecker( mfgs.Single( m => m.Name == "HTC" ) );
checker.CheckProducts();
Console.ReadLine();
checker = new PhoneTypeChecker( mfgs.Single( m => m.Name == "Nokia" ) );
checker.CheckProducts();
Console.Read();
}
There are still other problems with this example - so do not take this an example of how to write well-architectured or production-quality code.
In short: only the implementation of IManufacturerRepository
should be aware of the actual implementations of IManufacturer
(and only the implementations of IManufacturer
should be aware of the implementations of IPhoneFactory
).