Дилемма при выборе шаблона дизайна - PullRequest
0 голосов
/ 08 января 2019

Я хочу выбрать шаблон дизайна для проекта, над которым я работаю, и мне было интересно, могу ли я получить какой-либо вклад.


Фон

Интерфейс с именем Ranker определяется следующим образом.

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

Каждая реализация Ranker может иметь несколько Ranker в качестве переменных-членов, и каждый член Ranker может иметь несколько Ranker в качестве переменных-членов. Вы поняли идею. Ранкер может использовать другого Ранкера для ранжирования своих предметов.

В моем конкретном случае у меня есть 27 различных ранкеров, каждый из которых имеет ранг другой dataType. 26 из этих ранкеров будут использоваться одним из ранкеров, чтобы в конечном итоге иметь возможность ранжировать предметы.

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

Значение, RankerTypeA имеет, RankerTypeB, RankerTypeF и RankerTypeK в качестве членов и нуждается в них для надлежащего ранжирования элементов типа A .


Задача

Логика создания для каждого из Ranker различна. Я хотел бы использовать какой-то модуль / фабрику / и т.д., который помогает мне создавать эти ранкеры. Мне в конечном итоге нужно создать RankerForTypeA . Я имел в виду что-то подобное

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

Вот немного сложная часть. Я хотел бы, чтобы людям было проще, скажем, использовать собственные ранкеры там, где они считают нужным. Возвращаясь к иерархии, которую я нарисовал ранее, скажем, для TypeB , люди хотели использовать CustomRankerForTypeB вместо RankerForTypeB , они должны легко это делать. Так что в этом случае иерархия будет выглядеть как

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

Итак, здесь, если они хотят создать RankerForTypeA , они не могут напрямую использовать DefaultRankerFactory , поскольку он использует RankerForTypeB вместо CustomRankerForTypeB . Так что обычно вы бы хотели, чтобы они могли просто создать новую реализацию RankerFactory , но тогда было бы много дублирующегося кода, потому что логика создания множества других частей RankerForTypeA похоже.

Итак, для этого сценария я имел в виду нечто подобное.

 public class CustomRankerFactory extends DefaultRankerFactory  {

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

 }

Однако это решение мне не кажется правильным, поскольку оно в значительной степени зависит от наследования. CustomRankerFactory не обязательно является DefaultRankerFactory, поэтому такой подход не выглядит правильным. Плюс подход здесь не похож на шаблон Фабрики, хотя я продолжаю возвращаться к этому термину. Я ищу помощь с лучшим подходом к определению «Фабрики», чтобы она была модульной и легко расширяемой. Открыто для всех форм ввода.

Ответы [ 2 ]

0 голосов
/ 08 января 2019

Если вы не хотите, чтобы пользовательские реализации RankerFactory наследовали от DefaultRankerFactory, вы можете создать абстрактный базовый класс (ABC), от которого наследуются CustomRankerFactory и DefaultRankerFactory, причем DefaultRankerFactory не переопределяет любое поведение по умолчанию. Например:

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

Это на самом деле не меняет поведение, которое вы сейчас демонстрируете, но удаляет DefaultRankerFactory из иерархии CustomRankerFactory. Таким образом, иерархия теперь читает CustomRankerFactory - это AbstractRankerFactory, а не CustomRankerFactory - это DefaultRankerFactory.


Предостережение для следующего примера: это не обязательно «лучший» подход к проектированию, но он действительный и может удовлетворить ваши потребности в конкретной ситуации. Тем не менее, в целом, вместо этого стоит создать азбуку. По сути, экономно используйте следующую технику.

В Java 8 были дополнения к существующим интерфейсам, которые нарушили бы существующую функциональность как в библиотеках Java, так и в пользовательских классах, которые реализовали эти интерфейсы. Например, интерфейс Iterable<T> добавил следующий метод:

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

Это потребовало бы обновления всех интерфейса Iterable<T>, включая пользовательские классы (не только в стандартной библиотеке Java). Команда Java решила, что это слишком большая нагрузка для разработчиков (это, по сути, нарушило бы большую часть существующего кода Java), но они все же хотели добавить важные функции, такие как функциональный метод forEach, поэтому они скомпрометировали и добавил ключевое слово default в Java.

Это ключевое слово позволяет интерфейсу предоставлять реализацию по умолчанию, которая используется всеми реализующими классами, если реализующий класс вручную не переопределяет реализацию по умолчанию. Например, реализация JDK 10 forEach в Iterable<T>:

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

На практике это означало, что во всех реализациях Iterable<T> был добавлен метод forEach без необходимости переписывать какой-либо код. В случае вашей ранкерной системы вы можете использовать это ключевое слово default, чтобы удалить ABC и уменьшить сложность фабричной иерархии. Например:

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

Это снижает сложность иерархии, но имейте в виду, что оно использует ключевое слово default для целей, для которых оно изначально не предназначалось, и это может вызвать путаницу в клиентском коде. Если используется этот подход, следует документально подтвердить, что все поведение по умолчанию содержится в интерфейсе, и что пользовательские реализации RankerFactory должны реализовывать (т.е. переопределять) только те методы, для которых они хотят настраивать поведение, а не реализовывать все методы, как это делается. обычная практика с реализацией интерфейса.

0 голосов
/ 08 января 2019

Я решил подобную проблему не так давно. Решение пошло примерно так:

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

Парень, создающий CustomRankerForTypeB, может легко наследовать от RankerFactory и переопределять getRankerForB с помощью метода, возвращающего CustomRankerForTypeB. Все виды вещей принимали аргументы типа RankerFactory. Подсказка: это было бы хорошим местом для внедрения зависимостей, если вы не можете использовать его. Вы также можете использовать инъекцию зависимостей бедного человека и сделать

public class RankerFactory {
    private static RankerFactory factory = new RankerFactory();
    public static RankerFactory get_RankerFactory() { return factory; }
    public static void set_RankerFactory(RankerFactory newFactory ){ factory = newFactory; }
    //...

Если вы хотите по-настоящему гибкого, который здесь может быть обоюдоострым мечом, можете вместо этого сделать

public class RankerFactory {
    // I can't quite do this as generics sorry for the duplication
    public class FactoryA { public virtual getRanker() { return new RankerA(); } }
    private FactoryA a;
    public FactoryA getFactoryForA() { return a; }
    public void setFactoryForA(FactoryA value) { a = value };
    // ...

    public Ranker<A> getRankerForA(paramsForA) { return a.getRanker(); }
    public Ranker<B> getRankerForB(paramsForB) { return b.getRanker(); }
    public Ranker<C> getRankerForC(paramsForC) { return c.getRanker(); }
    //...
}

Это также сочетается с передачей фабрик (по моему опыту, вы хотите, чтобы на модульные фабрики приходилось примерно половину времени).

...