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,
- Producer - AppleTree, ApplePress, ApplePieMaker, AppleLoad,
- Consumer - ApplePress ( consumes Apples ), ApplePieMaker ( consumes Apples ) , 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:
- Argument passing ( the file where they will be saved/loaded from)
- Object re-use in different configurations ( use the same AppleTree always )
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 ).
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 ));
}
}