Java Method construction for repeated structure with small changes - java

I'm not sure if there's a pattern that covers this question, but here goes. In writing a method to call an analytical algorithm (such as a parser or calculation implementation), there is quite a large amount of code to read a source, apply the algorithm, then convert the results to something useful. Basically theres 20-30 lines of code and one algorithm/parser/tokenizer that changes quite often.
So.... my options that I see so far are:
Create a new method for each algorithm (ugly due to repetition)
Create a single method and pass in the algorithm as a parameter, then use a case or if/then to select the algorithm (this gets messy because I have to hard code my algorithm choices.
Create a separate initialization or setting of the algorithm and then check to see if its initialized in the method (which still is ugly, because somewhere else I have a hard-coded list of different algorithm choices in my selection method).
Is there a neat trick or general method construction programming pattern to solve this?
Thanks in advance.
--Edit--
To remove some of the abstraction of this question, here is a prototype of what I am talking about. So really only the implementation of the tokenizer changes.
pubic void tokenizeData(Filename datablob){
// convert filename
// open file
// handle file errors
// other code
// assign desired tokenizer
tokenizer = new TokenizerImplementation (or some other variation);
tokenizedData = tokenizer( cleansedData );
// parsing and collection code
// more error processing code
// cleanup code
}

I'd also go personnally for some combination of Interface and Abstract + Template Method as suggested by #Lucas Oliveira but for your very problem of selecting the appropriate Tokenizer implementation you may also need a Factory(pattern) to dynamically load another Tokenizer impl. based on the factory context or parameters without changing the content of your template method tokenizeData().
Example.1 a classic parametered Factory:
public class TokenizerFactory
{
private static final Logger logger = LoggerFactory.getLogger(TokenizerFactory.class);
private final int version;
public TokenizerFactory(int version) {
this.version = version;
}
public ITokenizer getInstance() {
switch(version) {
case 1: return new TokenizerV1();
case 2: return new TokenizerV2();
default: return new DefaultTokenizer();
}
}
}
Example.2 a dynamic class-loading static Factory (forgive me for the name):
public class TokenizerFactory
{
private static final Logger logger = LoggerFactory.getLogger(TokenizerFactory.class);
private TokenizerFactory() {}
// Here eg. ETokenizerType is a enum storing class associated to the type.
public static ITokenizer getInstance(ETokenizerType dtype) {
try {
return (ITokenizer)dtype.getClassVar().newInstance();
}
catch(Throwable ex) {
logger.error("Factory could not create an adequate instance of Tokenizer for dtype:{} !",dtype.name());
}
return new DefaultTokenizer();
}
}
You can define an interface for your Tokenizer(s) as:
public interface ITokenizer {
public void tokenizeData(Filename datablob);
}
... to be implemented by your abstract class AbstractTokenizer for which all subclasses (such as TokenizerV1 and TokenizerV2) will redefine only customized abstract method(s). Just like in the following example (based on #Lucas Oliveira proposal):
public abstract class AbstractTokenizer implements ITokenizer {
#Override
public void tokenizeData(Filename datablob) {
// convert filename
// open file
// handle file errors
// other code
tokenizedData = tokenizer( data );
// parsing and collection code
// more error processing code
// cleanup code
}
abstract TokenizedData tokenizer( Data cleansedData ); // << redef. by subclasses.
}
But it will transparent for you to use.
You can finally make use of your TokenizerFactory simply by providing a pre-configured one as argument to your main business methods or use them on-the-fly provided you own the parameter needed to parameterize it. So that the getInstance() call will return the exact Tokenizer you need able to ´tokenizeData()´.
Note: for highly parameterized factories, combining them with a Builder (pattern) is generally a life-saver.

Template Method seems what you are looking for.
It alows you to:
Define the skeleton of an algorithm in an operation, deferring some steps to client subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.
Base class declares algorithm 'placeholders', and derived classes implement the placeholders.
This is done with an Abstract class. You should decide which steps of the algorithm are invariant (or standard), and which are variant (or customizable). The invariant steps are implemented in the abstract base class, while the variant steps can be supplied by oncrete derived classes.
In your case things will go like that:
abstract class AbstractTokenizer {
public void tokenizeData(final Object datablob) {
// convert filename
// open file
// handle file errors
// other code
tokenizedData = tokenizer( cleansedData );
// parsing and collection code
// more error processing code
// cleanup code
}
abstract TokenizedData tokenizer( Data cleansedData );
}

Related

Is there any better way to make a constructor that fills in for all the possible variables rather than writing them all out?

Is there any less time consuming method of creating a program that can take all of my given parameters and regardless of what they are simply fill in for those values? For example given optional 4 parameters that I want to fill will I have to write a constructor for each possible combination?
I'm new to Java but I've already written one class where I did this, and it took me quite some time and was a somewhat monotonous, I was just wondering if there was a faster method of doing it.
Constructors are not the only way to construct objects. There are a few (creational) design patterns that accomplish the same goal that you should consider. In fact, it could be argued that constructors should be considered your last resort to build objects. I strongly recommend you do some research into Creational Design Patterns
For this problem, I think you can take advantage of The Builder Pattern.
Builder Pattern Explained
The builder pattern allows clients to build an object in stages. This is possible because instantiation of the required object is deferred to the builder until all needed attributes are obtained.
Consider the process of building a "widget". Assume this "widget" has two components, of which only one is required. You can either A) build the "widget" without the optional component, or B) you can assemble the "widget" with the required part, pause the build process and resume at a later time when the optional component is available. When using a conventional constructor, this is not possible.
Another advantage of the Builder pattern is that you don't need to create a constructor with a lot of parameters that are not required. And it eliminates the need to have multiple permutations of the constructor with different parameters. In fact, sometimes this is not even possible. For example, if the class in question has eight String parameters, you cannot have two constructors with four Strings each.
When implementing this pattern, the "widget" being built has only one constructor, and it is private. The constructor of the class is only accessible to the builder.
Lastly, unlike conventional "setter" methods, the "setter" methods of a builder return an instance of the builder itself. This allows chaining method calls to set multiple (optional) parameters. For example; builder.setOptionalParm1(val1).setOptionalParm2(val2)... etc.
Builder Pattern Code Example
public class ImmutableWidget {
private final String required;
private final String optional;
private ImmutableWidget (Builder builder) {
this.required = builder.required;
this.optional = builder.optional;
}
#Override
public String toString () {
return "Required: " + required + "; Optional: " + optional;
}
public String getRequired () {
return required;
}
public String getOptional () {
return optional;
}
public static class Builder {
private final String required;
private String optional;
public Builder (String required) {
this.required = required;
}
public Builder setOptional (String optional) {
this.optional = optional;
return this;
}
public ImmutableWidget build () {
return new ImmutableWidget (this);
}
}
public static void main (String... strings) {
Builder builder = new ImmutableWidget.Builder ("required");
builder.setOptional("optional"); // This step is not required
ImmutableWidget widget = builder.build();
System.out.println(widget);
}
}
Additional information
Basic Builder Pattern topic: Immutable Widget YouTube video
Advanced Builder Pattern topic: Using Builder Pattern with Class
Hierarchies
This post by Vitalii Fedorenko explains what to do: https://stackoverflow.com/a/12994104/20421925
Vitallii's post is a lot to take in (I'm still trying to understand it myself). Let me know if you need any assistance understanding it.

Generic methods to read/write to file all classes that extend X

sorry if this is a duplicate question, but I'm getting kind of desperate to solve a problem for a school project (due date tomorrow).
I'm very new to Java and this project involves storing data from certain objects to a local repository and also reading the data from the repo.
The repository handler class is in one package and the objects are in another.
My problem is that I don't know how to make generic methods in the handler to be able to read and write any object that extends X.
For example, let's say I have Fruit.
Apple extends Fruit.
Orange extends Fruit.
Both have their own unique attributes that I need to write/read to/from a file.
I wanted to have a method like
ArrayList repo_reader(String filepath)
That reads from a file and returns Apples and Oranges.
The only way that I know how to do this is having a field in the file stating which type of fruit it is, reading it and throwing it to a switch case like
switch (fruit_type){
case "Orange":
Orange orange = new Orange(); orange.setOrangeSpecificAttribute("ble");
FruitBasket.add(orange);
case "Apple":
Apple apple = new Apple(); apple.setAppleSpecificAttribute("bla");
FruitBasket.add(apple);
But then the method wouldn't be generic. Everytime that someone creates a new Fruit class, they would have to also change the repo_handler methods accordingly. The same would also happen with the writing method.
I could have the Fruit classes all implement their own method to write and read, but I don't want that. I want repo_handler class to deal with all the file reading and writing.
Again, sorry if it's a stupid question, and thanks for your guys' attention!
Btw, it's a CSV file, forgot to mention.
You may want to have different readers/factories dependending on the contents of some String. That is, the String acts as identifier for the reader/factory to use.
You can use a map to map identifiers (keys) to readers/factories (values). As the latter must be registered, I'd use the service loader mechanism as described in ServiceLoader, and iterate over all services to register them in this map.
Having this, you can look up the needed reader/factory in the map and use it to read/create new Fruit instances.
Step #1: Service Provider Interface
package com.mycompany.fruit;
public interface FruitFactory {
String getIdentifier();
Fruit createFruit(String[] attributes); // change params to your needs
}
Step #2: Service implementation
package com.mycompany.fruit;
public class AppleFactory implements FruitFactory {
public String getIdentifier() { return "Apple"; }
public Fruit createFruit(String[] attributes) {
Apple apple = new Apple();
apple.setCommonAttribute(attributes[1]);
apple.setSpecificAttribute(attributes[2]);
// ...
return apple;
}
}
Step #3: Register factories
Put a file com.mycompany.fruit.FruitFactory in META-INF/services. Put the fully qualified class name of each implementation of FruitFactory on separate lines in this file. For example:
com.mycompany.fruit.AppleFactory
com.mycompany.fruit.OrangeFactory
...
Step #4: Load the services and use them
public class MyFruitReader {
Map<String, FruitFactory> factories;
public MyFruitReader(...) {
ServiceLoader<FruitFactory> loader = new ServiceLoader(FruitFactory.class);
for (FruitFactory factory : loader) {
factories.put(factory.getIdentifier(), factory);
}
// usage
private Fruit getFruit(String[] row) {
String fruitType = row[0];
FruitFactory factory = factories.get(fruitType);
if (factory == null) {
return null;
}
return factory.createFruit(row);
}
// read the CSV file and call getFruit()
// ...
Keep in mind, that this are just sketches to provide a rough overview of this topic. You'll have to adapt it to your needs, add exception handling, fix errors, and so on.

Is there an alternative for overwriting the abstract method in the example cited?

I'm having a question about the implementation of this example here: https://dev.grakn.ai/docs/examples/phone-calls-migration-java. We have an abstract method inside a nested abstract static class:
public class PhoneCallsCSVMigration {
/**
* representation of Input object that links an input file to its own templating function,
* which is used to map a Json object to a Graql query string
*/
abstract static class Input {
String path;
public Input(String path) {
this.path = path;
}
String getDataPath() {
return path;
}
abstract String template(Json data);
}
...
Later on, the abstract method template(Json data) is overridden with the aim of obtaining a graqlInsertQuery:
inputs.add(new Input("files/phone-calls/data/companies") {
#Override
public String template(Json company) {
return "insert $company isa company, has name " + company.at("name") + ";";
}
});
First of all, how is it even possible to instantiate something of the type Input? And secondly, where does the Json company come from? I want to split up the PhoneCallsCSVMigration class into several classes like Input, GraknHandler, QueryHandler etc., and I'm wondering how I can define the template for constructing a Graql insert query other than overriding the abstract class. Any help is highly appreciated.
First of all, how is it even possible to instantiate something of the
type Input?
You're not instantiating class Input. You're creating an instance of an anonymous class that derives from Input and that implements its abstract method template.
where does the Json company come from?
It comes from the one that will invoke the template method, passing it as the parameter.
I leave the rest of the question to somebody who understood it...
Evidenty externally an Input child is created with a path and implementing template.
Probably this object is passed to the surrounding class and it calls template passing Json data.
The abstract method could just as easily be replaced by a Function<Json, String> but then that function would miss the path, so one could use BiFunction<String, Json, String>.
You have to look where and when the path and Json come from. Here it seems a bit artificial. Instead of an static inner class as above, a traditional (very similar) pattern would be:
abstract class A {
public final void func() { // Some service offered by this class.
B b = ...;
C c = onFunc(b);
}
abstract protected C onFunc(B b); // Some requirement to implement.
}
Here func is for users of A, a service.
And onFunc is for implementors of A to fulfill a specific requirement.
So out of context as in your case is a bit weird or over-engineered. Just passing a callback function would seem to do.

Merging duplicate code that use different objects

I use two api calls to get data about vehicleUtils depending on contentFilter.
I have very similar code for both (drivers and vehicles). What i tried to do is to extract the code into a single method and apply Strategy pattern like they suggest here Refactoring methods, but i could not figure out how to implement it. Am i using a good approach or is there any better way?
if (contentFilter.equalsIgnoreCase(Contentfilters.VEHICLES.toString())) {
VuScores vuScores = new VuScores();
List<VehicleVuScores> vehicleVuScoresList = new ArrayList<>();
List<VehicleUtilization> vehicleUtilizations = RestClient.getVehicles(request).join().getVehicleUtilizations();
if (Objects.nonNull(vehicleUtilizations)) {
vehicleUtilizations.forEach(vehicleUtilization -> {
vuScores.getVehicleVuScores().forEach(vehicleVuScore -> {
vehicleVuScore.getScores().setTotal(vehicleUtilization.getFuelEfficiencyIndicators().getTotal().getValue());
vehicleVuScore.getScores().setBraking(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(3).getIndicators().get(0).getValue());
vehicleVuScore.getScores().setCoasting(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(3).getIndicators().get(1).getValue());
vehicleVuScore.getScores().setIdling(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(0).getIndicators().get(0).getValue());
vehicleVuScore.getScores().setAnticipation(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(3).getValue());
vehicleVuScore.getScores().setEngineAndGearUtilization(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(1).getValue());
vehicleVuScore.getScores().setStandstill(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(0).getValue());
vehicleVuScore.getScores().setWithinEconomy(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(1).getIndicators().get(7).getValue());
vehicleVuScore.setAvgFuelConsumptionPer100Km(vehicleUtilization.getMeasures().getTotal().getAverageConsumption().getValue());
vehicleVuScore.setAvgSpeedDrivingKmh(vehicleUtilization.getMeasures().getTotal().getAverageSpeed().getValue());
vehicleVuScore.setEngineLoad(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(1).getIndicators().get(1).getValue());
vehicleVuScore.setTotalDistanceInKm(vehicleUtilization.getMeasures().getDriving().getDistance().getValue());
vehicleVuScore.setTotalTime(Math.toIntExact(vehicleUtilization.getMeasures().getTotal().getTime().getValue()));
vehicleVuScoresList.add(vehicleVuScore);
});
});
vuScores.setVehicleVuScores(vehicleVuScoresList);
}
return CompletableFuture.completedFuture(vuScores);
} else if (contentFilter.equalsIgnoreCase(Contentfilters.DRIVERS.toString())) {
VuScores vuScores = new VuScores();
List<DriverVuScores> driverVuScoresList = new ArrayList<>();
List<VehicleUtilization> vehicleUtilizations = RestClient.getDrivers(request).join().getVehicleUtilizations();
if (Objects.nonNull(vehicleUtilizations)) {
vehicleUtilizations.forEach(vehicleUtilization -> {
vuScores.getDriverVuScores().forEach(driverVuScores -> {
driverVuScores.getScores().setTotal(vehicleUtilization.getFuelEfficiencyIndicators().getTotal().getValue());
driverVuScores.getScores().setBraking(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(3).getIndicators().get(0).getValue());
driverVuScores.getScores().setCoasting(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(3).getIndicators().get(1).getValue());
driverVuScores.getScores().setIdling(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(0).getIndicators().get(0).getValue());
driverVuScores.getScores().setAnticipation(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(3).getValue());
driverVuScores.getScores().setEngineAndGearUtilization(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(1).getValue());
driverVuScores.getScores().setStandstill(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(0).getValue());
driverVuScores.getScores().setWithinEconomy(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(1).getIndicators().get(7).getValue());
driverVuScores.setAvgFuelConsumptionPer100Km(vehicleUtilization.getMeasures().getTotal().getAverageConsumption().getValue());
driverVuScores.setAvgSpeedDrivingKmh(vehicleUtilization.getMeasures().getTotal().getAverageSpeed().getValue());
driverVuScores.setEngineLoad(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(1).getIndicators().get(1).getValue());
driverVuScores.setTotalDistanceInKm(vehicleUtilization.getMeasures().getDriving().getDistance().getValue());
driverVuScores.setTotalTime(Math.toIntExact(vehicleUtilization.getMeasures().getTotal().getTime().getValue()));
driverVuScoresList.add(driverVuScores);
});
});
vuScores.setDriverVuScores(driverVuScoresList);
}
return CompletableFuture.completedFuture(vuScores);
}
Try to think about a common (abstract) base class, that holds the common code. The actual classes hold the differing code.
You then don't need to to work with instanceof or Contentfilters or whatever kind decission functionality you use. You just can call the common methods, as your function should take the (abstract) base class. This really removes code duplication.
Use an interface, implement it in both the classes, and use that interface in both places to get or set values.
Since all the method names are same, the interface should contain all the necessary getters and setters.
This way you won't have to use different classes.
So, everything is the same except
the types of the DTO you copy the data to (VehicleVuScores vs DriverVuScores)
the RestClient method invoked
The main challenge is sharing the code that invokes the setters. We need a way to refer to the target object without knowing whether its a VehicleVuScores or a DriverVuScores. We could declare it as:
Object vuScores;
but since Object doesn't declare the setters, we'd get compilation errors when trying to invoke the setters. To fix that, we can move the declaration of these getters and setters into a common base type:
abstract class VuScoresBase {
// fields, getters and setters
}
class DriverVuScores extends VuScoresBase {}
class VehicleVuScores extends VuScoresBase {}
so we can write:
public void convert(VehicleUtilization vehicleUtilization, VuScoresBase result) {
// invoke the setters here
}
and use this method in both cases.
With generics, we could also reuse the iteration code:
<V extends VuScoresBase> public void convertList(List<VehicleUtilization> vehicleUtilizations, List<V> resultList, Supplier<V> constructor) {
// iterate
V vuScore = constructor.apply();
convert(vehicleUtilization, vuScore);
resultList.add(vuScore);
}
so we could invoke it with
convertList(vehicleUtilizations, driverVuScores, DriverVuScore::new);
but i'd probably refrain from that, because the generics make the code hard to understand.
However, since the DriverVuScores and VehicleVuScores are so similar, I'd question whether we really need them to be separate types. If we can use VuScoresBase everywhere, this would vastly simplify the conversion logic:
VuScoresBase convert(VehicleUtilization vehicleUtilization) {
VuScoresBase vuScores = new VuScoreBase();
// invoke setters
return vuScores;
}
and
List<VuScoresBase> convertList(List<VehicleUtilization> vehicleUtilizations) {
// iterate
result.add(convert(vehicleUtilization));
}

Java - Alternatives to forcing subclass to have a static method

I often find I want to do something like this:
class Foo{
public static abstract String getParam();
}
To force a subclasses of Foo to return a parameter.
I know you can't do it and I know why you can't do it but the common alternative of:
class Foo{
public abstract String getParam();
}
Is unsatisfactory because it requires you to have an instance which is not helpful if you just want to know the value of the parameter and instantiating the class is expensive.
I'd be very interested to know of how people get around this without getting into using the "Constant Interface" anti pattern.
EDIT: I'll add some more detail about my specific problem, but this is just the current time when I've wanted to do something like this there are several others from the past.
My subclasses are all data processors and the superclass defines the common code between them which allows them to get the data, parse it and put it where it needs to go.
The processors each require certain parameters which are held in an SQL database. Each processor should be able to provide a list of parameters that it requires and the default values so the configuration database can be validated or initialised to defaults by checking the required parameters for each processor type.
Having it performed in the constructor of the processor is not acceptable because it only needs to be done once per class not once per object instance and should be done at system startup when an instance of each type of class may not yet be needed.
The best you can do here in a static context is something like one of the following:
a. Have a method you specifically look for, but is not part of any contract (and therefore you can't enforce anyone to implement) and look for that at runtime:
public static String getParam() { ... };
try {
Method m = clazz.getDeclaredMethod("getParam");
String param = (String) m.invoke(null);
}
catch (NoSuchMethodException e) {
// handle this error
}
b. Use an annotation, which suffers from the same issue in that you can't force people to put it on their classes.
#Target({TYPE})
#Retention(RUNTIME)
public #interface Param {
String value() default "";
}
#Param("foo")
public class MyClass { ... }
public static String getParam(Class<?> clazz) {
if (clazz.isAnnotationPresent(Param.class)) {
return clazz.getAnnotation(Param.class).value();
}
else {
// what to do if there is no annotation
}
}
I agree - I feel that this is a limitation of Java. Sure, they have made their case about the advantages of not allowing inherited static methods, so I get it, but the fact is I have run into cases where this would be useful. Consider this case:
I have a parent Condition class, and for each of its sub-classes, I want a getName() method that states the class' name. The name of the sub-class will not be the Java's class name, but will be some lower-case text string used for JSON purposes on a web front end. The getName() method will not change per instance, so it is safe to make it static. However, some of the sub-classes of the Condition class will not be allowed to have no-argument constructors - some of them I will need to require that some parameters are defined at instantiation.
I use the Reflections library to get all classes in a package at runtime. Now, I want a list of all the names of each Condition class that is in this package, so I can return it to a web front end for JavaScript parsing. I would go through the effort of just instantiating each class, but as I said, they do not all have no-argument constructors. I have designed the constructors of the sub-classes to throw an IllegalArgumentException if some of the parameters are not correctly defined, so I cannot merely pass in null arguments. This is why I want the getName() method to be static, but required for all sub-classes.
My current workaround is to do the following: In the Condition class (which is abstract), I have defined a method:
public String getName () {
throw new IllegalArugmentException ("Child class did not declare an overridden getName() method using a static getConditionName() method. This must be done in order for the class to be registerred with Condition.getAllConditions()");
}
So in each sub-class, I simply define:
#Override
public String getName () {
return getConditionName ();
}
And then I define a static getConditionName() method for each. This is not quite "forcing" each sub-class to do so, but I do it in a way where if getName() is ever inadvertently called, the programmer is instructed how to fix the problem.
It seems to me you want to solve the wrong problem with the wrong tool. If all subclasses define (can't really say inherit) your static method, you will still be unable to call it painlessly (To call the static method on a class not known at compile time would be via reflection or byte code manipulation).
And if the idea is to have a set of behaviors, why not just use instances that all implement the same interface? An instance with no specific state is cheap in terms of memory and construction time, and if there is no state you can always share one instance (flyweight pattern) for all callers.
If you just need to couple metadata with classes, you can build/use any metadata facility you like, the most basic (by hand) implementation is to use a Map where the class object is the key. If that suits your problem depends on your problem, which you don't really describe in detail.
EDIT: (Structural) Metadata would associate data with classes (thats only one flavor, but probably the more common one). Annotations can be used as very simple metadata facility (annotate the class with a parameter). There are countless other ways (and goals to achieve) to do it, on the complex side are frameworks that provide basically every bit of information designed into an UML model for access at runtime.
But what you describe (processors and parameters in database) is what I christened "set of behaviors". And the argument "parameters need to be loaded once per class" is moot, it completely ignores the idioms that can be used to solve this without needing anything 'static'. Namely, the flyweight pattern (for having only once instance) and lazy initialization (for doing work only once). Combine with factory as needed.
I'm having the same problem over and over again and it's hard for me to understand why Java 8 preferred to implement lambda instead of that.
Anyway, if your subclasses only implement retrieving a few parameters and doing rather simple tasks, you can use enumerations as they are very powerful in Java: you can basically consider it a fixed set of instances of an interface. They can have members, methods, etc. They just can't be instanciated (as they are "pre-instanciated").
public enum Processor {
PROC_IMAGE {
#Override
public String getParam() {
return "image";
}
},
PROC_TEXT {
#Override
public String getParam() {
return "text";
}
}
;
public abstract String getParam();
public boolean doProcessing() {
System.out.println(getParam());
}
}
The nice thing is that you can get all "instances" by calling Processor.values():
for (Processor p : Processorvalues()) {
System.out.println(String.format("Param %s: %s", p.name(), p.getParam()));
p.doProcessing();
}
If the processing is more complex, you can do it in other classes that are instanciated in the enum methods:
#Override
public String getParam() {
return new LookForParam("text").getParam();
}
You can then enrich the enumeration with any new processor you can think of.
The down side is that you can't use it if other people want to create new processors, as it means modifying the source file.
You can use the factory pattern to allow the system to create 'data' instances first, and create 'functional' instances later. The 'data' instances will contain the 'mandatory' getters that you wanted to have static. The 'functional' instances do complex parameter validation and/or expensive construction. Of course the parameter setter in the factory can also so preliminary validation.
public abstract class Processor { /*...*/ }
public interface ProcessorFactory {
String getName(); // The mandatory getter in this example
void setParameter(String parameter, String value);
/** #throws IllegalStateException when parameter validation fails */
Processor construct();
}
public class ProcessorA implements ProcessorFactory {
#Override
public String getName() { return "processor-a"; }
#Override
public void setParameter(String parameter, String value) {
Objects.requireNonNull(parameter, "parameter");
Objects.requireNonNull(value, "value");
switch (parameter) {
case "source": setSource(value); break;
/*...*/
default: throw new IllegalArgumentException("Unknown parameter: " + parameter);
}
}
private void setSource(String value) { /*...*/ }
#Override
public Processor construct() {
return new ProcessorAImpl();
}
// Doesn't have to be an inner class. It's up to you.
private class ProcessorAImpl extends Processor { /*...*/ }
}

Categories