Question

This is a simple abstract factory. I know abstract factory is for selecting a product family. But my question is, can we still use this pattern if ConcreteFactory1 needs to createProductC() but ConcreteFactory2 don't? And if we can't use, how do we solve this problem?

Was it helpful?

Solution

can we still use this pattern if ConcreteFactory1 needs to createProductC() but ConcreteFactory2 don't?

Yes. Yes you can.

It's a lot simpler to understand this pattern when you're looking at some client code.

class Client {
    private AbstractFactory af;
    public client(AbstractFactory af) { this.af = af; }
    public toString() { 
        return String.format("%s %s %s", 
            af.createProductA(),  
            af.createProductB(),
            af.createProductC());
    }        
} 

Which might let you print

ProductA1 ProductB1 ProductC1

Or it might let you print

ProductA2 ProductB2

How do we pull off that bit of magic? The Null Object Pattern! My favorite implementation of the null object pattern is the empty string: ""

Just because an object exists doesn't mean it has to do anything. When you're following the null object pattern you offer exactly the interface expected but calling the methods does a whole lot of nothing. So even if the client was asking more of these products then just their toString() methods the null object pattern will quietly do nothing when asked.

How well this works largely depends on how the client is written. You'll notice we're still left with a trailing space. But we avoid having to test which abstract factory we have. That's critical to remaining object oriented.

The null object can be as complex as the client needs it to be. The basic pattern is to make it safe yet meaningless to dot off of it. No exception thrown. Methods that return void simply do nothing. No side effect. Methods that return a string return the empty string. Methods that return numbers return 0 or 1 depending on if the client will add or multiply. Methods that return collections return empty collections. Even monads can be returned if that's what the client expects, so long as, in the end, they do nothing, quietly.

OTHER TIPS

can we still use this pattern if ConcreteFactory1 needs to createProductC() but ConcreteFactory2 don't?

You have some design options here:

  1. Create a separate abstract factory for creating C's. It's often useful break up your abstract factory interfaces by product unlike how the UML diagram has grouped CreateProductA and CreateProductB in the same interface. I think this is the right choice if product C doesn't have much in common with the other products.
  2. Keep createProductC in the same abstract factory, but change its signature to return either ProductC or nothing (in Java this would be Option[ProductC]). This will enforce clients to handle the fact that some concrete factories don't produce a product C. Alternatively throw a checked exception. I think this is the right choice if product C is closely related to products A and B and production of C is truly optional, and clients should only call it if it's available on the factory.

Your diagram only shows 1 client, so it will either need createProductC or it won't, and if it needs it the factory that can't provide C can't be used.

If you have more than one client, some needing Cs and some not, you probably want multiple types of factory

interface ABFactory {
    A createA();
    B createB();
}

interface ABCFactory {
    A createA();
    B createB();
    C createC();
}

And then each concrete factory can implement a bunch of interfaces.

 class Factory1 implements ABFactory, ABCFactory { ... }
 class Factory2 implements ABFactory { ... }

And each client can require what it requires

// can only use Factory1
class Client1 {
    private ABCFactory af;
    public client(ABCFactory af) { this.af = af; }
    public void doStuff() { 
        A a = af.createProductA();  
        B b = af.createProductB();
        C c = af.createProductC();
    }        
} 

// can use both Factory1 and Factory2
class Client2 {
    private ABFactory af;
    public client(ABFactory af) { this.af = af; }
    public void doStuff() { 
        A a = af.createProductA();  
        B b = af.createProductB();
    } 
} 
Licensed under: CC-BY-SA with attribution
scroll top