简体   繁体   English

在运行时配置“管道”

[英]configuring “plumbing” at runtime

This is kind of a design-patterns question in Java. 这是Java中的一种设计模式问题。

I am designing a java .jar file to act as a frameworks for managing and processing a certain form of data. 我正在设计一个java .jar文件,作为管理和处理某种形式数据的框架。 I want the end user to be able to dictate the "plumbing" configuration, within certain constraints, in a certain way. 我希望最终用户能够以某种方式在某些约束内指定“管道”配置。 The pieces are producers and/or consumers, and I know how to implement them, but the connections are confusing to me... here's a nonsense example that sort of parallels my application. 这些作品是生产者和/或消费者,我知道如何实现它们,但这些联系令我感到困惑......这是一个与我的应用程序相似的无意义的例子。

Suppose I have implemented these elements: 假设我已经实现了这些元素:

  • AppleTree => produces apples AppleTree =>生产苹果
  • ApplePieMaker => consumes apples, produces apple pies ApplePieMaker =>消费苹果,生产苹果馅饼
  • ApplePress => consumes apples, produces apple cider ApplePress =>消费苹果,生产苹果酒
  • AppleSave => stores apples, apple pies, or apple cider into a file AppleSave =>将苹果,苹果馅饼或苹果酒存入文件
  • AppleLoad => "reconstitutes" apples, apple pies, or apple cider from a file that was produced by AppleSave AppleLoad =>从AppleSave生成的文件中“重建”苹果,苹果馅饼或苹果酒
  • ApplePieMonitor => displays apple pies on the screen in a GUI format, as they are produced ApplePieMonitor =>以生成的GUI格式在屏幕上显示苹果馅饼

Now I want the user to be able to specify things like: 现在我希望用户能够指定以下内容:

  • AppleTree | ApplePress | AppleSave cider1.sav AppleTree | ApplePress | AppleSave cider1.sav (produce apples, make them into cider, save them to a file) AppleTree | ApplePress | AppleSave cider1.sav (生苹果,将它们制成苹果酒,将它们保存到文件中)
  • AppleTree | AppleSave apple1.sav AppleTree | AppleSave apple1.sav (produce apples, save them to a file) AppleTree | AppleSave apple1.sav (生成苹果,将它们保存到文件中)
  • AppleLoad apple1.sav | ApplePieMaker | ApplePieMonitor AppleLoad apple1.sav | ApplePieMaker | ApplePieMonitor (take saved apples, make them into pies, display the results on the screen in a GUI) AppleLoad apple1.sav | ApplePieMaker | ApplePieMonitor (取出已保存的苹果,将它们制成馅饼,在GUI中在屏幕上显示结果)
  • (not sure how to illustrate this, but might be specified as follows) (不知道如何说明这一点,但可能如下指定)

    AppleTree tree1, ApplePieMaker piemaker1 < tree1, AppleSave apples.sav < tree1, AppleSave @select(*.sav) < piemaker1, ApplePress press1 < tree1, AppleSave cider.sav < press1, ApplePieMonitor piemon1 < piemaker1 AppleTree tree1,ApplePieMaker piemaker1 <tree1,AppleSave apples.sav <tree1,AppleSave @select(*。sav)<piemaker1,ApplePress press1 <tree1,AppleSave cider.sav <press1,ApplePieMonitor piemon1 <piemaker1

(produce apples, make them into pies and cider, save the apples, pies and cider to separate files, with the pies going to a file selected by the user at runtime and the others going to predetermine files, and also display the pies on the screen in a GUI) (生产苹果,将它们制成馅饼和苹果酒,将苹果,馅饼和苹果酒保存到单独的文件中,将馅饼转到用户在运行时选择的文件中,其他文件将预先确定文件,并在其上显示馅饼。 GUI中的屏幕)

So I have a rough idea of how to architect a configuration file: namely to structure the thing into elements that have at most 1 input and at most 1 output, and then for each instantiated element, name it, and if it has an input, specify the name of the element providing the input. 所以我对如何构建配置文件有一个大概的想法:即将事物构造成具有最多1个输入和最多1个输出的元素,然后为每个实例化元素命名,并且如果它具有输入,指定提供输入的元素的名称。

What I'm unclear is how to go about coupling the elements of the program when they run. 我不清楚的是如何在程序运行时耦合程序的元素。 Maybe the way to go is to have a number of interfaces, like AppleConsumer , ApplePieConsumer , etc. so an ApplePieMaker would implement the AppleConsumer interface (incl. method consumeApple() ), and an AppleTree would implement an AppleProducer interface which can register consumers at startup, so that every time the AppleTree produces an apple, it has a list of its consumers and calls consumeApple() on each of them, which then does the right thing without the AppleTree having to know what they are doing with the apples.... 也许要走的路是拥有许多接口,比如AppleConsumerApplePieConsumer等,所以ApplePieMaker会实现AppleConsumer接口(包括方法consumeApple() ), AppleTree会实现AppleProducer接口,可以注册消费者启动时,每次AppleTree生成一个苹果时,它都有一个消费者列表,并在每个消费者上调用consumeApple() ,然后在没有AppleTree知道他们正在用苹果做什么的情况下做正确的事情。 ..

Any suggestions? 有什么建议么? Does this sort of thing have a name? 这种东西有名字吗? I'm not really that experienced in design patterns. 我在设计模式方面并不是那么有经验。

edit: my end users don't know and don't care about Java. 编辑:我的最终用户不知道也不关心Java。 They just need to be able to set up a config file (which I'm trying to make as simple as possible so I can give them some good examples) and run my program which will read the config file, construct the elements, hook them together, and go. 他们只需要能够设置一个配置文件(我想尽可能简单,所以我可以给他们一些很好的例子)并运行我的程序,它将读取配置文件,构造元素,钩住它们在一起,去吧。 All the elements are under my control, I don't need to support plugins, so I don't have to be super-general. 所有元素都在我的控制之下,我不需要支持插件,所以我不必非常一般。

I had that problem a while ago, and it was a bit hard to specify cleanly in Java, since my input and output could be plural. 我不久前遇到了这个问题,而且在Java中干净利落地指定它有点困难,因为我的输入和输出可能是复数。 However, when you are sure to have a single input and output (since a file is a specific kind of output, right?), you could try to use checked generics. 但是,当您确定只有一个输入和输出时(因为文件是特定类型的输出,对吗?),您可以尝试使用选中的泛型。

A processing step implementation (I will call that a filter ) has two checked type arguments: input and output (lets for instance suppose they all extend a common interface, and for every new type you inject in the system, you will subclass that interface). 处理步骤实现(我将其称为过滤器 )具有两个已检查的类型参数:输入和输出(例如,假设它们都扩展了一个公共接口,并且对于您在系统中注入的每个新类型,您将子类化该接口) 。

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

  public Class<Input> getInputType();

  public Class<Output> getOutputType();

  public void process(Input in, Output out);

}

A filter chain is then just an array of (compatible) Filter s. 然后,过滤器链只是(兼容) Filter的数组。 By compatible, I intend that for each filter, its Output is the same type as its follower Input , the first filter has an Input that match your overall input type, and the last filter has an Output that matches the expected result type. 通过兼容,我打算对于每个过滤器,其Output与其跟随器Input的类型相同,第一个过滤器具有与您的整体输入类型匹配的Input ,最后一个过滤器具有与预期结果类型匹配的Output This is easy to validate, in practice, since we are using checked generics. 在实践中,这很容易验证,因为我们使用的是检查泛型。

A filter chain is thus an other (compound) filter. 因此,过滤链是另一种(化合物)过滤器。 The compatibility of compounded filters should be checked in the constructor, and the array of compounds final. 应该在构造函数中检查复合滤波器的兼容性,最后是复合数组。 There is no accurate way to express the "chaining" property (compatibility) of the arguments of that constructors with generics, so you are going to have to do that with bare types, which is a bit unclean. 没有准确的方法来表达构造函数的参数与泛型的“链接”属性(兼容性),因此您将不得不使用裸类型执行此操作,这有点不干净。

An other way to do it that gets around this limitation, at the cost of more cumbersome writing, is to change the definition of a filter like this: 另一种解决此限制的方法是以更麻烦的写入为代价,更改过滤器的定义,如下所示:

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

   public Class<Input> getInputType();

   public Class<Output> getOutputType();

   public Output out process(Input in);

}

We will then have to define a compound filter as an imbrication of filter pairs, thus defined: 然后,我们必须将复合滤波器定义为滤波器对的叠加,从而定义:

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));
  }
}

Thus, composing filters is just a matter of writing: 因此, 编写过滤器只是写作的问题:

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);

I could not help it, I have to workout something for this. 我无法帮助它,我必须为此锻炼一些东西。

So here it is. 所以这就是。

You already have the idea about the apple producer/consumer so this is how I would do it. 你已经有了关于苹果生产者/消费者的想法,所以我就是这样做的。

Create three interfaces and implement as follows: 创建三个接口并实现如下:

  • Product - Either Apple, AppleCider, ApplePie, 产品 - Apple,AppleCider,ApplePie,
  • Producer - AppleTree, ApplePress, ApplePieMaker, AppleLoad, 制片人 - AppleTree,ApplePress,ApplePieMaker,AppleLoad,
  • Consumer - ApplePress ( consumes Apples ), ApplePieMaker ( consumes Apples ) , AppleMonitor, AppleSave. 消费者 - ApplePress(消费苹果),ApplePieMaker(消费苹果),AppleMonitor,AppleSave。

The idea is to have a generic Product, produced by generic Producers and consumed by generic Consumers. 我们的想法是拥有一个通用产品,由通用生产商生产并由通用消费者消费。

Once you have that, you can create the configuration file pretty much the way you describe it, and parse it to create a new instance for each element. 完成后,您可以像描述它一样创建配置文件,并解析它以为每个元素创建新实例。

 element1 | element2 | element3 <parameters> | element 4

In a map you create the element name and map it to the class that will create the new instance. 在地图中,您可以创建元素名称并将其映射到将创建新实例的类。

Let's say 让我们说吧

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

So each time you read an element in the configuration you create the instance: 因此,每次读取配置中的元素时,都会创建实例:

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

Finally you have to validate the structure of your configuration, but basically it could be like this: 最后,您必须验证配置的结构,但基本上它可能是这样的:

  • The first element should be a producer 第一个元素应该是生产者
  • The last should be a consumer 最后一个应该是消费者
  • Any intermediate should be producer-consumers 任何中介应该是生产者 - 消费者
  • You can parse arguments needed ( file to save data for instance ) 您可以解析所需的参数(例如,保存数据的文件)

Once you have all your objects created and chained, you start producing. 创建和链接所有对象后,即可开始生成。

There are some thing you have to workout but they're pretty easy: 你需要锻炼一些东西,但它们很容易:

  1. Argument passing ( the file where they will be saved/loaded from) 参数传递(将保存/加载它们的文件)
  2. Object re-use in different configurations ( use the same AppleTree always ) 对象在不同配置中重复使用(始终使用相同的AppleTree)

Final notes: The following code, is just an scratch, you may really consider a dependency injector to do the job, but of course it will take you a little while to learn it. 最后的注意事项:下面的代码,只是一个划痕,你可能真的考虑一个依赖注入器来完成这项工作,但当然你需要花一点时间来学习它。

The configuration parsing should be made by hand, for the format you're using will be unique for the end-user and should be pretty simple. 配置解析应该是手工完成的,因为您使用的格式对于最终用户来说是唯一的,并且应该非常简单。 Still you can deliver as much complexity you want inside your jar ( using any number of frameworks you need ). 你仍然可以在jar中提供你想要的复杂程度(使用你需要的任意数量的框架)。

You can also take a look to the following design patterns: 您还可以查看以下设计模式:

The implementation below, is a kind of monster the these three ( I didn't compile it, just throw some code to show how the idea would look like ) 下面的实现,是这三个怪物(我没有编译它,只是抛出一些代码来展示这个想法会是什么样子)

I hope this helps. 我希望这有帮助。

/**
 * 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 ));
    }
}

I believe what you are trying to do can be done within the Spring framework. 我相信你想要做的事情可以在Spring框架内完成。 It uses dependency injection to say "In order to create XI need Y, so find something that produces Y and see what you need in order to create it". 它使用依赖注入来说“为了创建XI需要Y,所以找到产生Y的东西,看看你需要什么来创建它”。

I may be wrong, but I suggest you have a look. 我可能错了,但我建议你看看。

Try the Java Beanshell . 试试Java Beanshell

BeanShell is a small, free, embeddable Java source interpreter with object scripting language features, written in Java. BeanShell是一个小型,免费,可嵌入的Java源代码解释器,具有用Java编写的对象脚本语言功能。 BeanShell dynamically executes standard Java syntax and extends it with common scripting conveniences such as loose types, commands, and method closures like those in Perl and JavaScript. BeanShell动态执行标准Java语法,并使用常见的脚本编写方便性扩展它,例如松散类型,命令和方法闭包,如Perl和JavaScript中的那些。

You would need some sort of Registry where producers could register (Hi, I'm an apple tree and I produce apples) and then the consumers could look up whom ever produces apples. 你需要某种注册表,生产者可以注册(嗨,我是一棵苹果树,我生产苹果),然后消费者可以查找谁生产苹果。 This could also be done in reverse where the consumers register interest and the producers look up. 这也可以在消费者注册兴趣和生产者抬头的情况下反向完成。 I did something similar using JMX where an Object could query the JMX Server for an Object that produced a certain type of Message and then register with that Object (Publish/Subscribe). 我使用JMX做了类似的事情,其中​​一个Object可以向JMX Server查询产生某种类型Message的Object,然后向该Object注册(Publish / Subscribe)。 I am now porting that app to use OSGi which has a similar capability 我现在移植该应用程序以使用具有类似功能的OSGi

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM