Архитектура / Проектирование трубопроводной системы. Как улучшить этот код? - PullRequest
1 голос
/ 20 июля 2009

У меня есть приложение на основе конвейера, которое анализирует текст на различных языках (скажем, английском и китайском). Моя цель - иметь систему, которая может работать на обоих языках, прозрачным способом. ПРИМЕЧАНИЕ : Этот вопрос длинный, поскольку в нем много простых фрагментов кода.

Конвейер состоит из трех компонентов (назовем их A, B и C), и я создал их следующим образом, чтобы компоненты не были тесно связаны:

public class Pipeline {
    private A componentA;
    private B componentB;
    private C componentC;

    // I really just need the language attribute of Locale,
    // but I use it because it's useful to load language specific ResourceBundles.
    public Pipeline(Locale locale) {
        componentA = new A();
        componentB = new B();
        componentC = new C();
    }

    public Output runPipeline(Input) {
        Language lang = LanguageIdentifier.identify(Input);
        //
        ResultOfA resultA = componentA.doSomething(Input);
        ResultOfB resultB = componentB.doSomethingElse(resultA); // uses result of A
        return componentC.doFinal(resultA, resultB); // uses result of A and B
    }
}

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

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

public abstract class A {
    private CommonClass x;  // common to all languages
    private AnotherCommonClass y; // common to all languages

    abstract SomeTemporaryResult getTemp(input); // language specific
    abstract AnotherTemporaryResult getAnotherTemp(input); // language specific

    public ResultOfA doSomething(input) {
          // template method
          SomeTemporaryResult t = getTemp(input); // language specific
          AnotherTemporaryResult tt = getAnotherTemp(input); // language specific
          return ResultOfA(t, tt, x.get(), y.get());
    }
}

public class EnglishA extends A {
    private EnglishSpecificClass something;
    // implementation of the abstract methods ... 
}

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

public Enum AFactory {
    SINGLETON;

    private Map<String, A> cache; // this map will only have one or two keys, is there anything more efficient that I can use, instead of HashMap ?

    public A getA(Locale locale) {
        // lookup by locale.language, and insert if it doesn't exist, et cetera
        return cache.get(locale.getLanguage());
    }
}

Итак, мой вопрос : Что вы думаете об этом дизайне? Как это может быть улучшено ? Мне нужна «прозрачность», потому что язык может быть изменен динамически, основываясь на тексте, который он анализирует. Как видно из метода runPipeline, я сначала идентифицирую язык ввода, а затем, основываясь на этом, мне нужно изменить компоненты конвейера на идентифицированный язык. Таким образом, вместо того, чтобы вызывать компоненты напрямую, возможно, я должен получить их с завода, вот так:

public Output runPipeline(Input) {
    Language lang = LanguageIdentifier.identify(Input);
    ResultOfA resultA = AFactory.getA(lang).doSomething(Input);
    ResultOfB resultB = BFactory.getB(lang).doSomethingElse(resultA);
    return CFactory.getC(lang).doFinal(resultA, resultB);
}

Спасибо, что прочитали это далеко. Я очень ценю каждое предложение, которое вы можете сделать по этому вопросу.

Ответы [ 3 ]

1 голос
/ 21 июля 2009

Идея фабрики хороша, как и идея, если это возможно, заключить компоненты A, B и C в отдельные классы для каждого языка. Я хотел бы обратить ваше внимание на одну вещь: использовать Interface наследование вместо Class наследование. Затем вы можете включить двигатель, который будет выполнять процесс runPipeline для вас. Это похоже на шаблон Builder / Director . Шаги в этом процессе будут следующими:

  1. получить ввод
  2. использовать фабричный метод для получения правильного интерфейса (английский / китайский)
  3. передача интерфейса в ваш движок
  4. запустите трубопровод и получите результат

По теме extends против implements, Аллен Холуб немного перебирает , чтобы объяснить предпочтение Interfaces.


Следите за вашими комментариями:

Моя интерпретация применения шаблона Builder здесь будет состоять в том, что у вас есть Factory, который будет возвращать PipelineBuilder. PipelineBuilder в моем проекте - это тот, который включает в себя A, B и C, но вы можете иметь отдельные компоновщики для каждого, если хотите. Этот строитель затем передается вашему PipelineEngine, который использует Builder для генерации ваших результатов.

Так как это использует Фабрику для предоставления Строителям, ваша идея для Фабрики остается в такте, изобилующей механизмом кэширования.

Что касается вашего расширения abstract, у вас есть выбор: предоставить PipelineEngine право собственности на тяжелые предметы. Однако, если вы пойдете по пути abstract, обратите внимание, что объявленные вами общие поля private и, следовательно, не будут доступны вашим подклассам.

1 голос
/ 20 июля 2009

Мне нравится базовый дизайн. Если классы достаточно просты, я мог бы рассмотреть возможность объединения фабрик A / B / C в один класс, поскольку, похоже, на этом уровне может быть некоторое разделение поведения. Я предполагаю, что они действительно более сложны, чем кажутся, и поэтому это нежелательно.

Основным подходом использования фабрики для уменьшения связи между компонентами является звук, IMO.

0 голосов
/ 21 июля 2009

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

Если я прав по этому поводу, возможно, вы захотите взглянуть на платформы DI. Они делают то, что вы сделали (что довольно просто, верно?), А затем добавляют еще несколько способностей, которые вам могут не понадобиться сейчас, но, возможно, вы найдете их позже.

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

Из того, что я видел в DI, вполне вероятно, что вы захотите перенести все создание вашей "Трубы" на фабрику, чтобы она связывала вас и просто передавала вам то, что вам нужно, чтобы решить конкретная проблема, но теперь я действительно достигаю - мои знания о DI немного лучше, чем мои знания о вашем коде (другими словами, я вытаскиваю большую часть этого из своей задницы).

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