-1

this is the article

https://www.codeproject.com/Articles/328373/Understanding-and-Implementing-Abstract-Factory-Pa

I don't know why we need to have an abstract factory like IPhoneFactory, in the client's end, can't we do sth like

class PhoneTypeChecker
{
   ISmart sam;
   IDumb htc;
   //IPhoneFactory factory;    get rid of this abstract factory
   MANUFACTURERS manu;

   ...
   public void CheckProducts()
    {
        switch (manu)
        {
            case MANUFACTURERS.SAMSUNG:
                Console.WriteLine(new SamsungFactory().GetSmart().Name()); 
                Console.WriteLine(new SamsungFactory().GetDumb().Name());               
                break;
            case MANUFACTURERS.HTC:
                Console.WriteLine(new HTCFactory().GetSmart().Name());  
                Console.WriteLine(new HTCFactory().GetDumb().Name());               
                break;
            case MANUFACTURERS.NOKIA:
                Console.WriteLine(new NokiaFactory().GetSmart().Name()); 
                Console.WriteLine(new NokiaFactory().GetDumb().Name());                
                break;
        }

        Console.WriteLine(manu.ToString() + ":\nSmart Phone: " + 
        factory.GetSmart().Name() + "\nDumb Phone: " + factory.GetDumb().Name());
    }
}

the only thing I can think of to use IPhoneFactory is that we needs to get two different types(Smart and Dumb) from concrete factory classes, but it only save some keystrokes, isn't it?

  • 1
    You're not wrong - but think about the disadvantages of your approach: the set of types your `PhoneTypeChecker` can produce is now fixed and cannot be extended at runtime. If you want to add a new manufacturer at runtime you can't - you have to edit your source and rebuild it. – Dai Sep 10 '19 at 03:01
  • @Dai if I use `IPhoneFactory factory`, I still need to edit the source (PhoneTypeChecker.cs) with 'case MANUFACTURERS.HUAWEI ...' and rebuild it? –  Sep 10 '19 at 03:18
  • I suggest starting off with https://stackoverflow.com/questions/5739611/what-are-the-differences-between-abstract-factory-and-factory-design-patterns – jaco0646 Sep 10 '19 at 13:53

1 Answers1

2

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).

Dai
  • 141,631
  • 28
  • 261
  • 374