mercredi 30 juin 2021

Library, “Injecting factory” and best practices for extending a library

I use a library which I need to patch. I need to fork it because Factory design patter is not used.

Library has that structure: class X that hold references to other classes, that hold references to other classes and few more layers. Class hierarchy is “tree-like” structure.

I look for best way for refactoring – flexible enough to prevent people forking it. I cannot find any best practices on that problem.

Over years I use to solve it by “Injecting Factory” – Factory pattern where parent object is being injected (DI) in factory method. I cannot find any documentation on such approach and I need some feedback on it (e.g. theory or possible problems).

I describe it with an example with Car:

  • Car has CarInterior
  • CarInterior has Odometer

Problem – no custom objects could be created, especially Odometer.

class Car {
  CarInterior interior;
  public Car() {
    interior = new CarInterior();
  }
}
class CarInterior {
  Odometer odo;
  public CasInterior(){
    odo = new Odomether();
  }
}
class Odometer {
  public Odometer(){}
}

By-book solution using simple Factory methods pattern only (has a limited extendibility)

class Car {
  CarInterior interior;
  public Car(CarFactory factory) {
    interior = createInterior();
  }
  CarInterior createInterior() { return new CarInterior(); };
}
class CasInterior {
  Odometer odo;
  public CarInterior(){
    odo = createOdometer();
  }
  CarInterior createOdometer() {
    return new Odomether();
  }
}

Major problem with this approach

  • If factory method needs parameters, that change potentially makes troubles to lib users. Because extending classes should change method definitions.

Minor problems

  • if I need to different Odometer, I should extend both Car and CarInterior
  • library I need to fork has few “levels” so this means many levels of extra objects till I manage to call The factory method.

Possible Business requests

Possible extension #1 of Odometer class: with KM or Miles

Car object should have targetCountry. If it is in France, Metric units should be used. If in USA – miles. That business request could be implemented in this way:

  • (breaking change) factory method createOdometer() gets a param: createOdometer(OdoUnits)
  • (breaking change) factory method createInterior() gets a param: createInterior(OdoUnits) or createInterior(Country)
  • in case of more levels (my case), more changes are needed

Possible extension #2 of Odometer class: odometer values range

  • 0-220 km/h
  • 0-300 km/h
  • etc

Possible extension #3 of Odometer class: color of odometer should be same as color of the car

If we use same approach, we should introduce more parameters in methods. And more “proxy” functionality.

The approach to discuss - "Injecting Factory"

  • Factory methods takes an additional parameter – parent object.
  • Odometer gets its Factory, gets Car object and requests all needed data to adapt to the "Car specs"

CODE:

class CarFactory {
  CarInterior createInterior(Car car) { return new CarInterior(car); }
  Odometer createOdometer(CarInterior interior){ return new Odometer(interior); };
}
class Car {
  CarFactory factory;
  CarInterior interior;
  public Car(CarFactory factory) {
    this.factory = factory;
    interior = factory.createInterior(this);
  }
  CarFactory getFactory(){ return factory; };
}
class CarInterior {
  Car car;
  Odometer odo;
  public CarInterior(Car car){
    this.car = car;
    CarFactory factory = car.getFactory();
    odo = factory.createOdometer( this );
  }
}
class Odometer {
  public Odometer(CarInterior interior) {
    Car car = interior.getCar();
    OdoUnits odoUnits = car.getTargetCountry().getUnits();
    int maxEngineSpeed = car.getEngine().getTopSpeed;
    Color color = car.getColor();
    if (!car.isOptionColorOdometer())
      color = DEFAULT_COLOR; 
    // etc
  }
}

Questions

So what are best practices for the issue? What are disadvantages of "Injecting Factory"?

Aucun commentaire:

Enregistrer un commentaire