Dilemma while selecting a design pattern

jadeblack12 :

I am looking to select a design pattern for a project I am working on and was wondering if I can get some input.


Background

An interface called Ranker is defined as follows.

interface Ranker<T> {
    public void rank(List<T> item);
}

Each implementation of Ranker can have multiple Rankers as member variables and each of the member Rankers can have multiple Rankers as their member variables. You get the idea. A Ranker can use another Ranker to help rank it's items.

In my particular case I have 27 different Rankers defined each of which Rank a different dataType. 26 of those rankers will be used by one of the Rankers to be ultimately be able to rank the items.

RankerForTypeA
  RaknerForTypeB 
    RankerForTypeC
    RankerForTypeD
    RankerForTypeE
  RankerForTypeF
    RankerForTypeG
      RankerForTypeH
        RankerForTypeI 
      RankerForTypeJ
  RankerForTypeK
  ...

Meaning, RankerTypeA has, RankerTypeB, RankerTypeF and RankerTypeK as members and needs them in order to appropriately rank items of type A.


Problem

The instantiation logic for each of the Rankers is different. I would like to use some sort Module/Factory/etc that helps me create these rankers. I ultimately need it to create RankerForTypeA. I had something like this in mind.

 interface RankerFactory {
      Ranker<A> getRankerForA(paramsForA);
      Ranker<B> getRankerForB(paramsForB);
      Ranker<C> getRankerForC(paramsForC);
      ...
 }

 public class DefaultRankerFactory implements RankerFactory  {
      Ranker<A> getRankerForA(paramsForA) {
          getRankerForB(paramsForB)
          ...
      }
      Ranker<B> getRankerForB(paramsForB) {
          getRankerForC(paramsForC)
          ...
      }
      Ranker<C> getRankerForC(paramsForC) {
          ...
      }
 }

Here's the bit tricky part. I would like to make it easy for people to let's say use custom rankers where they see fit. Going back to the hierarchy I drew before, let's say for TypeB, people wanted to use CustomRankerForTypeB instead of RankerForTypeB, they should be easily be able to. So in that case the hierarchy would look like

RankerForTypeA
  CustomRankerForTypeB 
    ...
  RankerForTypeF
    RankerForTypeG
      RankerForTypeH
        RankerForTypeI 
      RankerForTypeJ
  RankerForTypeK
  ...

So here, if they want to create RankerForTypeA, they can't directly use DefaultRankerFactory because it uses RankerForTypeB instead of CustomRankerForTypeB. So normally you would be like, they can just create a new implementation of RankerFactory but then it would have a lot of duplicate code because the instantiation logic of a lot of the of other parts of RankerForTypeA is similar.

So, for this scenario I had something like this in mind.

 public class CustomRankerFactory extends DefaultRankerFactory  {

      Ranker<B> getRankerForB(paramsForB) {
          //Define instantiation logic for CustomRankerForTypeB
          ...
      }

 }

This solution however doesn't seem right to me because it relies heavily on inheritance. CustomRankerFactory is not necessarily a DefaultRankerFactory, so that approach doesn't seem right. Plus the approach here doesn't seem like a Factory pattern even though I keep going back to that term. I am looking for help with the best approach to define the "Factory" so that it's modular and easily extensible. Open to all forms of input.

Justin Albano :

If you want to avoid having custom RankerFactory implementations inherit from DefaultRankerFactory, you can create an abstract base class (ABC) from which both CustomRankerFactory and DefaultRankerFactory inherit, where the DefaultRankerFactory does not override any default behavior. For example:

interface RankerFactory {
    Ranker<A> getRankerForA(paramsForA);
    Ranker<B> getRankerForB(paramsForB);
    Ranker<C> getRankerForC(paramsForC);
    // ...
}

public abstract class AbstractRankerFactory {

    public Ranker<A> getRankerForA(paramsForA) {
        // ...default behavior...
    }

    public Ranker<B> getRankerForB(paramsForB) {
        // ...default behavior...
    }

    public Ranker<C> getRankerForC(paramsForC) {
        // ...default behavior...
    }
}

public class DefaultRankerFactory extends AbstractRankerFactory {}

public class CustomRankerFactory extends AbstractRankerFactory {

    @Override
    public Ranker<C> getRankerForC(paramsForC) {
        // ...custom behavior...
    }
}

This does not actually change the behavior you are currently exhibiting, but it removes the DefaultRankerFactory from the CustomRankerFactory hierarchy. Thus, the hierarchy now reads CustomRankerFactory is an AbstractRankerFactory rather than CustomRankerFactory is a DefaultRankerFactory.


Caution for this next example: This is not necessarily the "best" design approach, but it is a valid one and may suit your needs in a particular situation. In general, though, it is a good idea to create an ABC instead. Essentially, use the following technique sparingly.

In Java 8, there were additions to existing interfaces that would have broken existing functionality in both the Java libraries and in user-defined classes that implemented these interfaces. For example, the Iterable<T> interface added the following method:

public void forEach(Consumer<? super T> consumer);

This would have required that all implementations of the Iterable<T> interface, including in user-defined classes (not just in the standard Java library), be updated. The Java team decided that this was too large of a burden to place on developers (it would have essentially broken a majority of existing Java code), but they still wanted to add important features such as the functional forEach method, so they compromised and added the default keyword to Java.

This keyword allows an interface to provide a default implementation that is used by all implementing classes, unless the implementing class manually overrides the default implementation. For example, the JDK 10 implementation of forEach in Iterable<T> is:

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

In practice this meant that all Iterable<T> implementations had the forEach method added without having to rewrite any code. In the case of your ranker system, you can utilize this default keyword to remove the ABC and reduce the complexity of the factory hierarchy. For example:

interface RankerFactory {

    default Ranker<A> getRankerForA(paramsForA) {
        // ...default behavior...
    }

    default Ranker<B> getRankerForB(paramsForB) {
        // ...default behavior...
    }

    default Ranker<C> getRankerForC(paramsForC) {
        // ...default behavior...
    }

    // ...
}

public class DefaultRankerFactory implements RankerFactory {}

public class CustomRankerFactory implements RankerFactory {

    @Override
    public Ranker<C> getRankerForC(paramsForC) {
        // ...custom behavior...
    }
}

This does reduce the complexity of the hierarchy, but keep in mind that it uses the default keyword for a purpose it was not originally intended and this may cause confusion in client code. If this approach is used, it should be documented that all default behavior is contained in the interface and that custom RankerFactory implementations should only implement (i.e. override) those methods for which they want custom behavior, rather than implementing all methods as is the normal practice with implementing an interface.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=70732&siteId=1