2013-11-14 7 views
15

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

Например:

interface ClerkResolver { 
    String resolveClerk(String department); 
} 

class DefaultClerkResolver implements ClerkResolver { 

    public String resolveClerk(String department) { 
     // some stuff 
    } 
} 

class CountryClerkResolver implements ClerkResolver { 

    public String resolveClerk(String department) { 
     // I do not need the department name here. What I need is the country. 
    } 

} 

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

class StrategyParameter { 

    private String department; 
    private String country; 

    public String getDepartment() ... 
} 

interface ClerkResolver { 
    String resolveClerk(StrategyParameter strategyParameter); 
} 

Но если честно, я не удовлетворен этим решением, потому что я должен изменить параметр класс каждый раз, когда стратегия нуждается в новом/другом аргументе. А во-вторых, вызывающая сторона стратегии должна установить все параметры, потому что он не знает, какая стратегия решит клерка, поэтому он должен предоставить все параметры (но это не так уж плохо).

Опять же, для меня это звучит как проблема дизайна в моем коде, но я не могу найти лучшего решения.

--- РЕДАКТИРОВАТЬ

Основная проблема с этим решением является при создании задачи. Служба задачи выглядит следующим образом:

class TaskService { 

    private List<ClerkResolver> clerkResolvers; 

    Task createTask(StrategyParamter ...) { 

     // some stuff 

     for(ClerkResolver clerkResolver : clerkResolvers) { 
      String clerk = clerkResolver.resolveClerk(StrategyParameter...) 
      ... 
     } 

     // some other stuff 
    } 

} 

Как вы можете видеть, когда TaskService используются, вызывающие должны предоставить необходимую информацию для решения клерка, то есть название отдела и/или в стране, потому что сам TaskService не имеет такой информации.

Когда задача должна быть создана, вызывающий должен предоставить StrategyParameter, потому что они необходимы для решения клерка. Опять же, проблема в том, что у вызывающего нет всей информации, то есть он не знает о стране. Он может устанавливать только название отдела. Вот почему я добавил второй метод интерфейса, чтобы гарантировать, что стратегия может справиться с разрешения клерка:

interface ClerkResolver { 
    String resolveClerk(StrategyParameter strategyParameter); 
    boolean canHandle(StrategyParameter strategyParameter); 
} 

На риск повторения меня, это решение не кажется мне правильным.

Итак, если у кого-то есть лучшее решение этой проблемы, я был бы признателен за это.

Спасибо за ваши комментарии!

+0

Как вы сказали, вызывающий не может знать, какая стратегия разрешит клерка, поэтому он должен всегда предоставлять все данные. Итак, почему у вас нет метода стратегии для всех возможных параметров? –

+0

Потому что я не хочу менять evertime подписи типа метода, добавляется новая стратегия, для которой нужен новый параметр. Я хочу сохранить его. Вот почему я создал второе решение с классом параметров. – Phillip

+0

Почему бы не использовать шаблон Builder для класса StrategyParameter? – Mikhail

ответ

6

Я думаю, что есть некоторая путаница относительно того, что задача на самом деле. По моему мнению, задача - это что-то, что делает клерк. Таким образом, вы можете сами создать задачу, не зная о клерке.

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

interface ClerkResolver { 
    String resolveClerk(Task task); 
} 

Для реализации такого рода клерка решающим вы можете использовать шаблон стратегии на основе фактического типа задачи, например.

+0

Это отличная идея. Сам объект задачи имеет всю необходимую информацию для решения клерка. Я не могу придумать атрибут, необходимый для разрешения, но не сохраненного в объекте задачи. – Phillip

1

Давайте начнем с предположения, что ваш код основан на простых блоках if-else-if.

В таком случае вам все равно необходимо будет иметь все необходимые входные данные. Об этом не обойтись.

Используя шаблон стратегии, вы начинаете развязывать свой код, т. Е. Определяете базовый интерфейс и конкретную реализацию.

Просто наличие этой конструкции недостаточно, потому что вам все еще нужно иметь блок if-else-if.

На данный момент, вы можете посмотреть на следующих конструктивных изменений:

  1. Используйте шаблон фабрики, чтобы загрузить все доступные стратегии от этой системы. Это может быть основано на метаинформации, такой как шаблон Service Loader, который доступен в JDK.

  2. Определите стратегию, с помощью которой вы можете запросить доступные реализации, чтобы узнать, могут ли они обрабатывать заданный набор параметров ввода. Это может быть так же просто, как canYouResolve (input)! = Null. Делая это, мы переходим от блока if-else-if к циклу for-each.

  3. В вашем случае у вас есть Реализация по умолчанию. Итак, скажем, что реализация по умолчанию является частью вашего модуля, а другие стратегии поступают из других банок (которые загружаются через ServiceLoader из пункта 1).

  4. Когда вы вводите код, вы сначала ищете все доступные стратегии; спросите их, могут ли они справиться с текущим сценарием; если ни один из них не сможет его обработать, используйте реализацию по умолчанию.

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

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

Примечание: Это очень похоже на то, как работает JavaEE ELResolver. В этом случае код маркирует EL как разрешенный, тем самым сообщая корневому классу, что разрешение завершено.

Примечание. Если вы считаете, что загрузчик услуг слишком тяжелый, посмотрите на поиск всех файлов META-INF/some-file-that-you-like, чтобы идентифицировать распознаватели, доступные в системе.

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

Надеюсь, это поможет вашему сценарию.

+0

Спасибо за ваш ответ. Я редактировал свой вопрос, прежде чем я его прочитал. Я уже добавил метод I к интерфейсу, чтобы убедиться, что стратегия может справиться с ним до вызова метода resolveClerk (2-4 реализовано почти так, как вы его написали). К сожалению, я не знаю, как работает JavaEE ELResolver. Я должен посмотреть. – Phillip

-2

Поскольку Java статически типизирована, одним из хороших способов имитации динамических объектов является использование Карты. Я хотел бы сделать это для передачи динамических параметров моих резольвер:

class StrategyParameter extends Map {} 
// Map could be used directly, but this make the code more readable 

Тогда моя картина стратегии становится: интерфейс ClerkResolver { Строка resolveClerk (StrategyParameter strategyParameter); }

class DefaultClerkResolver implements ClerkResolver { 

    public String resolveClerk(StrategyParameter strategyParameter) { 
     // strategyParameter.get("department"); 
    } 
} 

class CountryClerkResolver implements ClerkResolver { 

    public String resolveClerk(StrategyParameter strategyParameter) { 
     // strategyParameter.get("country"); 
    } 

} 
+0

Карту с разными типами пар ключей очень сложно понять при чтении кода одного метода. Скорее всего, в скором времени в кошмаре обслуживания. – SpaceTrucker

+0

Проблема с использованием карты состоит в том, что разработчик стратегии должен знать имя параметра в вашем примере «страна». Это нормально, пока есть только один вызов стратегии, и я единственный, кто реализует новые стратегии. Но если это не так, вам нужно заглянуть в код, чтобы убедиться, как имя параметра. Есть преимущества и недостатки. Может быть, я переусердствовал ... @SpaceTrucker Я согласен. – Phillip

+0

Если вы запрограммировали JavaScript, все, что есть Карта. На мой взгляд, это позволяет вам писать гораздо более динамический код, если вы дисциплинированы.В любом случае, может быть, есть более элегантное решение этой проблемы, но этот подход во многих случаях очень хорошо работал для меня. – Param

3

Мне очень понравилось «предложение SpaceTrucker, в том, что иногда проблемы решаются путем перемещения абстракции на другой уровень :)

Но если ваш оригинальный дизайн имеет смысл (что только вы можете сказать, основываясь на вашем ощущению спецификация) - то ИМХО можно либо: 1) Держите свой подход «загрузки все в StrategyParameter» 2) Или переместить эту ответственность Стратегия

для варианта (2), я предполагаю, что есть какая-то общая сущность (счет? клиент?), из которого можно вывести отдел/страну. Тогда у вас есть «CountryClerkResolver.resolveClerk (String accountId)», который будет искать страну.

ИМХО оба (1), (2) являются законными, в зависимости от контекста. Иногда (1) работает на меня, потому что все параметры (отдел + страна) дешевы для предварительной загрузки. Иногда мне даже удается заменить синтетический «StrategyParameter» на бизнес-интуитивно понятный объект (например, учетную запись). Иногда (2) работает лучше для меня, например. если «отдел» и «страна» требовали отдельных и дорогостоящих поисков. Это особенно заметно при наличии сложных параметров. если стратегия выбирает клерков на основе их оценок в обзорах удовлетворенности клиентов, это сложная структура, которую нельзя загружать для более простых стратегий.

+0

К сожалению, вариант (2) невозможен в моем случае, потому что информация для решения клерка зависит от данных, введенных пользователем (в настоящее время они не хранятся в базе данных), результатов веб-сервисов или просто по типу задачи. Невозможно найти это, эта информация должна быть обойдена стратегии. Но, как вы сказали, вариант (2) является законным, и я бы использовал его, если это было возможно. – Phillip

+0

Спасибо :) просто вопрос: для варианта (2) ваша стратегия не может нести ответственность за сбор необходимых данных от пользователя (т. Е. У стратегии будет метод «getRequiredPropertyNames»)? Так что пользователю не нужно вводить ненужные данные (например, введите его страну для стратегии, которая ее не использует)? –

+0

Я думал о подобном решении - например, используя шаблон посетителя, чтобы получить нужную информацию от вызывающего. Но для этого требуется, чтобы интерфейс был реализован вызывающим абонентом и не решает проблему получения разных свойств. Дело в том, что звонящий не должен знать, какая стратегия разрешает клерку. Он должен предоставить информацию, которую он имеет в настоящее время. И в зависимости от этой информации (и других) клерк будет разрешен. – Phillip

Смежные вопросы