Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

extending parameterized factory method in java

I'm new to OOP and learning design patterns so I wrote some simple code to try out a Factory Method, and all seems well, except when I want to add another sub-type. Here's the code so far:

public interface Person {
  public String getDescription();
}

public class Adult implements Person { 
  @Override
  public String getDescription() {
    return "I am an ADULT";
  }
}

public class Child implements Person {
  @Override
  public String getDescription() {
    return "I am a CHILD";
  }
}

public class PersonFactory {
  public Person create(int age) {
    if (age < 18) return new Child();
    return new Adult();
  }
}

public class ClientA {
  public static void main(String[] args) {
    PersonFactory personFactory = new PersonFactory();
    Person person;
    person = personFactory.create(80);
    System.out.println(person.getDescription());   
  }
}

If the requirement changes later to include a sub-class Pensioner for when the age is > 70, I would have to either:

Add the line if (age > 70) return new Pensioner(); to the create() method in the PersonFactory class, which surely breaks the Open-Closed Principle? Or, as suggested in The Gang Of Four Design Patterns book, override the parameterized factory method to selectively extend the products a Creator produces. In this case I think that would mean writing a new class:

public class PersonFactoryWithPensioner extends PersonFactory { 
  @Override
  public Person create(int age) {
    if (age > 70) return new Pensioner();
    return super.create(age);
  }
}

This now means that either all the clients which call the PersonFactory would now have to be changed to use PersonFactoryWithPensioner instead, or I have to accept that new clients could call PersonFactoryWithPensioner whilst the old clients eg. ClientA would still only receive an Adult object when the age is > 70. It gets even worse if later on another sub-class eg. Infant is added. To ensure the new clients receive whichever object of Infant, Child, Adult or Pensioner is appropriate, a new class PersonFactoryWithInfant would have to extend PersonFactoryWithPensioner. This can't be right, seems more likely I have misunderstood what GoF suggest.

My question is: Is there a way to add a new sub-type that can be returned to old clients without changing them, and without breaking the OCP by changing the PersonFactory code to include the new sub-type?

Apologies if I have not posted this correctly, it is my first time posting a question here. I have looked through previous answers for similar problem but they don't seem to quite address this.

like image 384
Andy Tipp Avatar asked Jan 20 '17 16:01

Andy Tipp


People also ask

Should a factory class be static?

Factory class does not requires to maintain any state. So that normally we go for Static method .

What is the advantage of factory design pattern?

Advantage of Factory Design Pattern Factory Method Pattern allows the sub-classes to choose the type of objects to create. It promotes the loose-coupling by eliminating the need to bind application-specific classes into the code.


2 Answers

I think OCP doesn't stop from from modifying any method or class.

But, it proposes that if you need to do any modication, you should do it so that you don't have to modify that code again.

Given that you may need to modify PersonFactory later - you could create yet another Factory class to create objects of type PersonFactory. Although this seems like over-engineered solution.

Another possible solution would be that PersonFactory load this rules from some dynamic source, for example, save this rules in a file using JSON format. And then create objects dynamically using reflection.

Something like this:

 private static JSONObject RULES;
   static {
       RULES= JSON.parse(rulesEngine.load());
   } 
    public class PersonFactory {
      public Person create(int age) {
        String personToCreate = RULES.get(age);
        Constructor<?> ctor = Class.forName(personToCreate).getConstructor();
        return (Person) ctor.newInstance();
      }
    }

The json rules would be something like this:

{
  "1":"Child.class",
  "2":"Child.class",
  ...,
  "17":"Child.class",
  "18":"Adult.class",
  ...,
  "69":"Adult.class",
  "70":"Pensioner.class"
}

This way you don't break OCP Principle.

like image 106
alayor Avatar answered Sep 25 '22 14:09

alayor


The open-closed principle is good to keep in mind. It does not work nicely with factories, however. One option that sort-of-works is the following, which turns the factory into a registry:

    PersonFactory pf = new PersonFactory();
    // Java 8 lambdas are great!
    pf.register((age) -> age < 18 ? new Child() : null );
    pf.register((age) -> age >= 18 ? new Adult() : null );

    System.out.println(pf.create(10).getDescription());     

Similarly to @alayor's answer, the only way to avoid having to modify the logic of the factory, or having to replace the factory altogether and get everyone to use the new version... is for the factory to get its logic from elsewhere. @alayor gets it from a configuration file; I propose adding it to the factory as part of its initialization (could be done in the factory constructor too; changing it to, say, public PersonFactory(PersonCreator ... rules) ).

Full code:

interface PersonCreator {
    Person create(int age);
}

class PersonFactory {
    private List<PersonCreator> pcs = new ArrayList<>();

    public void register(PersonCreator pc) {
        pcs.add(pc);
    }

    public Person create(int age) {
        for (PersonCreator pc : pcs) {
            Person p = pc.create(age);
            if (p != null) {
                return p;
            }
        }
        return null;
    }
}

interface Person {
    public String getDescription();
}

class Adult implements Person {
    @Override
    public String getDescription() {
        return "I am an ADULT";
    }
}

class Child implements Person {
    @Override
    public String getDescription() {
        return "I am a CHILD";
    }
}

public class Main {
    public static void main(String[] args) {
        PersonFactory pf = new PersonFactory();
        // Java 8 lambdas are great!
        pf.register((age) -> age < 18 ? new Child() : null );
        pf.register((age) -> age >= 18 ? new Adult() : null );

        System.out.println(pf.create(10).getDescription());            
    }
}
like image 34
tucuxi Avatar answered Sep 24 '22 14:09

tucuxi