在运行时配置“管道”

时间:2009-02-18 19:06:22

标签: java design-patterns

这是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(取出已保存的苹果,将它们制成馅饼,在屏幕上以GUI显示结果)
  • (不知道如何说明这一点,但可能会指定如下)

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

(生产苹果,将它们制成馅饼和苹果酒,将苹果,馅饼和苹果酒保存到单独的文件中,将馅饼转到用户在运行时选择的文件,其他文件将预先确定文件,并显示在GUI中屏幕上的馅饼

所以我对如何构建配置文件有一个大概的想法:即将事物构造成具有最多1个输入和最多1个输出的元素,然后为每个实例化元素命名,并且如果它具有输入,指定提供输入的元素的名称。

我不清楚的是如何在程序运行时耦合程序的元素。也许要走的路是拥有许多接口,例如AppleConsumerApplePieConsumer等,因此ApplePieMaker将实现AppleConsumer接口(包括方法{{ 1}})和consumeApple()将实现一个AppleTree接口,可以在启动时注册消费者,这样每次AppleProducer生成一个苹果时,它都有一个消费者列表和在每个人身上调用AppleTree,然后做出正确的事情,而consumeApple()不必知道他们在用苹果做什么......

有什么建议吗?这种东西有名字吗?我在设计模式方面并不是那么有经验。

编辑:我的最终用户不知道也不关心Java。他们只需要能够设置一个配置文件(我想尽可能简单,所以我可以给他们一些很好的例子)并运行我的程序,它将读取配置文件,构造元素,钩住它们在一起,去吧。所有元素都在我的控制之下,我不需要支持插件,所以我不必非常一般。

5 个答案:

答案 0 :(得分:1)

前一段时间我遇到了这个问题,而且在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的数组。通过兼容,我打算对于每个过滤器,其输出与其跟随者输入的类型相同,第一个过滤器具有匹配的输入您的整体输入类型,最后一个过滤器具有与预期结果类型匹配的输出。在实践中,这很容易验证,因为我们使用的是检查泛型。

因此,过滤链是另一种(化合物)过滤器。应该在构造函数中检查复合滤波器的兼容性,最后是复合数组。没有准确的方法来表达构造函数的参数与泛型的“链接”属性(兼容性),因此您将不得不使用裸类型执行此操作,这有点不干净。

另一种解决此限制的方法是以更加繁琐的写作为代价,更改过滤器的定义如下:

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

答案 1 :(得分:1)

我无法帮助它,我必须为此锻炼一些东西。

所以就是这样。

你已经有了关于苹果生产者/消费者的想法,所以我就是这样做的。

创建三个接口并按如下方式实现:

  • 产品 - Apple,AppleCider,ApplePie,
  • 制作人 - AppleTree,ApplePress,ApplePieMaker,AppleLoad,
  • 消费者 - ApplePress(消费苹果),ApplePieMaker(消费苹果),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)
  3. 最后的注意事项:下面的代码,只是一个划痕,您可能真的考虑使用依赖注入器来完成这项工作,但当然需要花一点时间来学习它。

    配置解析应该手工完成,因为您使用的格式对于最终用户来说是唯一的,并且应该非常简单。你仍然可以在jar中提供你想要的复杂程度(使用你需要的任意数量的框架)。

    您还可以查看以下设计模式:

    下面的实现,是这三种怪物(我没有编译它,只是抛出一些代码来表明这个想法会是什么样子)

    我希望这会有所帮助。

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

答案 2 :(得分:0)

我相信你想要做的事情可以在Spring框架内完成。它使用依赖注入来说“为了创建X我需要Y,所以找到产生Y的东西,看看你需要什么来创建它”。

我可能错了,但我建议你看看。

答案 3 :(得分:0)

尝试使用Java Beanshell

  

BeanShell是一个小型,免费,可嵌入的Java源代码解释器,具有用Java编写的对象脚本语言功能。 BeanShell动态执行标准Java语法,并使用常见的脚本编写方便性扩展它,例如松散类型,命令和方法闭包,如Perl和JavaScript中的那些。

答案 4 :(得分:0)

你需要某种类型的注册表,生产者可以注册(嗨,我是一棵苹果树,我生产苹果),然后消费者可以查找谁生产苹果。这也可以在消费者注册兴趣和生产者抬头的情况下反向完成。我使用JMX做了类似的事情,其中​​一个Object可以向JMX Server查询产生某种类型Message的Object,然后向该Object注册(Publish / Subscribe)。我现在移植该应用程序以使用具有类似功能的OSGi

相关问题