настройка "сантехники" во время выполнения - PullRequest
3 голосов
/ 18 февраля 2009

Это вопрос о шаблонах проектирования в Java.

Я разрабатываю файл java .jar, который будет служить основой для управления и обработки определенной формы данных. Я хочу, чтобы конечный пользователь мог определенным образом диктовать «водопроводную» конфигурацию в рамках определенных ограничений. Эти продукты являются производителями и / или потребителями, и я знаю, как их реализовать, но связи меня смущают ... вот бессмысленный пример, который как бы параллелен моему приложению.

Предположим, я реализовал эти элементы:

  • AppleTree => производит яблоки
  • ApplePieMaker => потребляет яблоки, производит яблочные пироги
  • ApplePress => потребляет яблоки, производит яблочный сидр
  • AppleSave => магазины яблок, яблочные пироги или яблочный сидр в файл
  • AppleLoad => "восстанавливает" яблоки, яблочные пироги или яблочный сидр из файла, созданного AppleSave
  • ApplePieMonitor => отображает яблочные пироги на экране в формате GUI по мере их производства

Теперь я хочу, чтобы пользователь мог указывать такие вещи, как:

  • AppleTree | ApplePress | AppleSave cider1.sav (произвести яблоки, превратить их в сидр, сохранить в файл)
  • AppleTree | AppleSave apple1.sav (производить яблоки, сохранить их в файл)
  • AppleLoad apple1.sav | ApplePieMaker | ApplePieMonitor (взять сохраненные яблоки, превратить их в пироги, отобразить результаты на экране в графическом интерфейсе)
  • (не знаю, как это проиллюстрировать, но можно указать следующее)

    AppleTree tree1, ApplePieMaker piemaker1

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

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

Что мне непонятно, так это как соединить элементы программы при их запуске. Возможно, для этого нужно иметь несколько интерфейсов, таких как AppleConsumer, ApplePieConsumer и т. Д., Чтобы ApplePieMaker реализовывал интерфейс AppleConsumer (включая метод consumeApple()) и AppleTree реализовал бы интерфейс AppleProducer, который может регистрировать потребителей при запуске, так что каждый раз, когда AppleTree производит яблоко, у него есть список его потребителей и для каждого из них вызывается consumeApple(), который затем делает правильные вещи без AppleTree должен знать, что они делают с яблоками ....

Есть предложения? У такого рода вещей есть имя? Я не настолько опытен в шаблонах проектирования.

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

Ответы [ 5 ]

1 голос
/ 19 февраля 2009

Я не мог с этим поделать, я должен что-то потренироваться для этого.

Итак, вот оно.

У вас уже есть идея о производителе / ​​потребителе яблок, поэтому я бы так и сделал.

Создайте три интерфейса и выполните следующее:

  • Продукт - либо Apple, AppleCider, ApplePie,
  • Производитель - AppleTree, ApplePress, ApplePieMaker, AppleLoad,
  • Потребитель - ApplePress (использует Apple), ApplePieMaker (использует Apple), AppleMonitor, AppleSave.

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

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

 element1 | element2 | element3 <parameters> | element 4

На карте вы создаете имя элемента и сопоставляете его с классом, который создаст новый экземпляр.

Допустим,

map.put( "AppleTree", YouAppleTreeClass.class );

Таким образом, каждый раз, когда вы читаете элемент в конфигурации, вы создаете экземпляр:

for( String item: line )  { 
    Object o = map.get( item ).newInstance();
}

Наконец, вам нужно проверить структуру вашей конфигурации, но в основном это может быть так:

  • Первым элементом должен быть производитель
  • Последний должен быть потребителем
  • Любое промежуточное звено должно быть производителем-потребителем
  • Вы можете проанализировать необходимые аргументы (например, файл для сохранения данных)

Как только все ваши объекты будут созданы и объединены в цепочку, вы начнете создавать.

Есть кое-что, что вам нужно потренироваться, но они довольно просты:

  1. Передача аргументов (файл, из которого они будут сохранены / загружены)
  2. Повторное использование объекта в разных конфигурациях (всегда используйте один и тот же AppleTree)

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

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

Вы также можете взглянуть на следующие шаблоны дизайна:

Реализация, представленная ниже, является своего рода монстром для этих трех (я не скомпилировал его, просто бросил код, чтобы показать, как будет выглядеть идея)

Надеюсь, это поможет.

/**
 * Anything. An apple, cider, pie, whatever.
 */
interface Product{}

// The kinds of products. 
class Apple     implements Product{}
class ApplePies implements Product{}
class AppleCider implements Product{}

/**
 * This indicates the class will do something.
 **/
interface Producer { 
    // adds a consumer to the list.
    public void addConsumer( Consumer c );
    // removes the consumer from the list.
    public void removeConsumer( Consumer c );
    // let know eveytone a product has been created.
    public void notifyProductCreation( Product someProduct );
    // You're producer? Produce then... 
    public void startProduction();
}

// To avoid copy/paste all around
class AbstractProducer implements Producer { 
    private List<Consumer> consumers = new ArrayList<Consumer>();
    // adds a consumer to the list.
    public void addConsumer( Consumer c ) {
        consumers.add( c );
    }
    // removes the consumer from the list.
    public void removeConsumer( Consumer c ) {
        consumers.remove( c );
    }
    public void notifyProductCreation( Product someProduct ) { 
        for( Consumer c : list ) { 
            c.productCreated( someProduct );
        }
    }
}

interface Consumer { 
    // Callback to know a product was created
    public void productCreated( Product p );
}


class AppleTree extends AbstractProducer { 
    public void startProduction() { 
        // do something with earh, sun, water.. 
        // and from time to time:
        Product ofThisNewApple = new Apple();
        notifyProductCreation( ofThisNewApple );
    }

}    
class ApplePieMaker extends AbstractProducer implements Consumer { 

    // Ok, a product was created, but
    // is it the product I care?
    // check first and consume after.
    public void productCreated( Product p ){
        // Is this the kind of product I can handle..
        // well do handle
        if( p instanceof Apple ) {
            /// start producing pies..
        }
    }
    public void startProduction() { 
        // collect the needed number of apples and then...
        Product ofPie = new ApplePie();
        notifyProductCreation( ofPie );
    }

}
class ApplePress extends AbstractProducer implements Consumer { 
    // Yeap, something gots produced.
    // Just handle if it is an apple
    public void productCreated( Product p ) { 
        if( p instanceof Apple ) { 
            // start producing cider
        }
    }


    public void startProduction() { 
        // collect the needed number of apples and then...
        Product ofCiderBottle = new AppleCider();
        notifyProductCreation( ofCiderBottle );
    }


}
class AppleSave implements Consumer { 
    public void productCreated( Product p ) { 
        file.append( p );// any one will do.
    }
}

class AppleLoad extends AbstractProducer { 
    public void startProduction() { 
        readFromFile();
    }
    private readFromFile() { 
        for( Product p : file ) { 
            notifyProductCreation( p );  
        }
    }
}


class Main { 
    public static void main( String [] args ) { 
        Configuration conf = new Configuration();
        List<Producer> producers conf.read();
        for( Producer p : producers ) { 
            // fasten your seat belts.... 
            p.startProduction();
        }
    }
}

/// Ahhh, pretty ugly code below this line.
// the idea is:
// Read the configuration file
// for each line split in the "|"
// for each element create a new instance
// and chain it with the next.
// producer | consumer | etc...  
// Becomes....
// new Producer().addConsumer( new Consumer() );
// Return the list of create producers.  
class Configuration { 
    List<Producer> producers
    // read the file 
    // create the instances
    // let them run.
    public List<Producer> read() { 
        File file = new File(....
        // The format is: 
        // producer | consumer-producer <params> | consumer 
        String line = uniqueLineFrom( file );

        String [] parts = line.split("|");

        if( parts.length == 1 ) { 
            System.err.println("Invalid configuration. use element | element | etc. Only one element was....");
            System.exit( 1 );
        }



        int length = parts.length;
        for( int i = 0 ; i < parts.length ; i++ ) { 
            Object theInstance = implementationMap.get( parts[i] ).newInstance();
            validatePosition( i, length, theInstance , parts[i] );
        }

        List<Producer> producers = new ArrayList<Producer>();
        for( int i = 0 ; i < parts.length ; i++ ) { 
            Object theInstance = getInstance( parts[i] );
            if( not( isLast( i, length ) && isProducer( theInstance ) ) { 
                // the next is its consumer
                Producer producer = ( Producer ) theInstance;
                producer.addConsumer( ( Consumer )  getInstance( parts[i+1] ));
                producers.add( producer );
            }
        }
        return producers;

    }
    // creates a new instance from the implementation map.
    private Object getInstance( String key ) { 
        return implementationMap.get( part[i] ).newInstance();        
    }
    // validates if an element at the given position is valid or not.
    // if not, prints the message and exit.
    // the first element most be a producer
    // the last one a consumer 
    // all the middle elements producer-consumer
    // 
    private void validatePosition( int i, int length, Object theInstance, String element  ) { 
        if( isFirst( i ) && not(isProducer(( theInstance ) ))) {  
            System.err.println( "Invalid configuration: " + element + " most be a producer ( either Ap...");
            System.exit( 2 );
        } else if ( isLast( i, length ) && not( isConsumer( theInstance  ))) { 
            System.err.println( "Invalid configuration: " + element + " most be a consumer ( either Ap...");
            System.exit( 3 );
        } else if ( isMiddleAndInvalid( i, length , instance ) ) { 
            System.err.println( "Invalid configuration: " + element + " most be a producer-consumer ( either Ap...");
            System.exit( 4 );
        }
    }
    private static Map<String,Class> implementationMap = new HashMap<String,Class>() static { 
        implementationMap.put( "AppleTree", AppleTree.class );
        implementationMap.put( "ApplePieMaker ", ApplePieMaker .class );
        implementationMap.put( "ApplePress", ApplePress.class );
        implementationMap.put( "AppleSave", AppleSave.class );
        implementationMap.put( "AppleLoad", AppleLoad.class );
        implementationMap.put( "ApplePieMonitor", ApplePieMonitor.class );
    };

    // Utility methods to read better ( hopefully ) the statements 
    // If you could read the validations above you may ignore these functions.


    private boolean not( boolean value ) { 
        return !value;
    }
    private boolean isFirst( int i  ) { 
        return i == 0;
    }
    private boolean isLast( int i, int l ) { 
        return i == l -1 ;
    }
    private boolean isProducer( Object o ) { 
        return o instanceof Producer;
    }
    private boolean isConsumer( Object o ) { 
        return o instanceof Consumer;
    }
    private boolean isMiddleAndInvalid( int index, int length, Object instance ) { 
        return not( isFirst( index ) ) && not( isLast( index, length ) ) && not( isProducer( instance ) && isConsumer( instance ));
    }
}
1 голос
/ 18 февраля 2009

У меня была такая проблема некоторое время назад, и было довольно сложно точно указать в Java, так как мой ввод и вывод могут быть множественными. Однако, когда вы уверены, что у вас есть один вход и выход (так как файл - это особый вид вывода, верно?), Вы можете попробовать использовать проверенные дженерики.

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

public interface Filter<Input extends Type, Output extends Type> {

  public Class<Input> getInputType();

  public Class<Output> getOutputType();

  public void process(Input in, Output out);

}

Цепочка фильтров - это просто массив (совместимых) Filter с. Под совместимостью я предполагаю, что для каждого фильтра его Выход того же типа, что и его последователь Вход , первый фильтр имеет Вход , который соответствует вашему общему входу тип, и последний фильтр имеет Output , который соответствует ожидаемому типу результата. Это легко проверить на практике, поскольку мы используем проверенные дженерики.

Цепочка фильтров, таким образом, является другим (составным) фильтром. Совместимость составных фильтров должна быть проверена в конструкторе, а массив соединений окончательным. Не существует точного способа выразить свойство «сцепления» (совместимость) аргументов этих конструкторов с обобщениями, поэтому вам придется делать это с голыми типами, что немного нечисто.

Другой способ обойти это ограничение за счет более громоздкой записи - изменить определение фильтра следующим образом:

public interface Filter<Input extends Type, Output extends Type> {

   public Class<Input> getInputType();

   public Class<Output> getOutputType();

   public Output out process(Input in);

}

Затем мы должны определить составной фильтр как набор пар фильтров, определенный таким образом:

public class CompoundFilter<Input extends Type, Output extends Type>
       implements Filter<Input extends Type, Output extends Type> {

  private final Filter<Input extends Type, ? extends Type> l;
  private final Filter<Input extends Type, ? extends Type> r;

  public <Median extends Type> CompoundFilter(
         Filter<Input, Median> r,
         Filter<Median, Output> l
      ) {
      this.l = l;
      this.r = r;
  }

  @SuppressWarnings("unchecked")
  public Output out process(Input in) {
      // Compute l(r(in)) = (l o r) (in)
      return ((Output<Input,Type>) l).process(r.process(in));
  }
}

Таким образом, составление фильтров - это всего лишь вопрос записи:

Filter<A,B> f1 = new FilterImpl<A,B>;;
Filter<B,C> f2 = new FilterImpl<B,C>;
// this is mathematically f2 o f1
Filter<A,C> comp = new CompoundFilter<A,C>(f1,f2);
0 голосов
/ 19 февраля 2009

Вам понадобится какой-нибудь Реестр, в котором производители могли бы зарегистрироваться (Привет, я яблоня, а я выращиваю яблоки), а затем потребители могли бы искать, кто когда-либо производит яблоки. Это также может быть сделано в обратном порядке, когда потребители регистрируют интерес, а производители смотрят вверх. Я сделал нечто подобное, используя JMX, где Object мог запросить у JMX Server объект, который генерировал определенный тип сообщения, а затем зарегистрироваться в этом объекте (публикация / подписка). Я сейчас портирую это приложение для использования OSGi, который имеет аналогичные возможности

0 голосов
/ 18 февраля 2009

Попробуйте Java Beanshell .

BeanShell - это небольшой бесплатный встраиваемый интерпретатор исходного кода Java с функциями языка объектных сценариев, написанный на Java. BeanShell динамически выполняет стандартный синтаксис Java и дополняет его обычными функциями сценариев, такими как свободные типы, команды и замыкания методов, как в Perl и JavaScript.

0 голосов
/ 18 февраля 2009

Я считаю, что то, что вы пытаетесь сделать, может быть сделано в рамках Spring. Он использует внедрение зависимостей, чтобы сказать: «Для создания X мне нужен Y, поэтому найдите что-то, что производит Y, и посмотрите, что вам нужно для его создания».

Возможно, я ошибаюсь, но я предлагаю вам взглянуть.

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