I'll show you two methods of creating it; one is neither good nor bad and the other one is better.
Method 1
You can use Generic and Interface for that new Feature. The advantage is that you don't need to wrap inside the new object for a new field or attribute.
Create a Feature interface.
public interface Feature<T> {
T getClassValue();
void setClassValue(T feature);
}
Add it to the concrete class.
public class Car<ReturnType> {
//acceleration and breaking features are added using the strategy pattern
public Feature<ReturnType> newFeature;
public Car(Feature<ReturnType> features) {
this.newFeature = features;
}
public Feature<ReturnType> getNewFeature() {
return newFeature;
}
public void setNewFeature(Feature<ReturnType> newFeature) {
this.newFeature = newFeature;
}
}
Problem: The problem is that the new feature can be NULL. It also breaks the Single Responsibility Principle because the car has the responsibility to add the feature. It would populate more Generics to some Abstract classes later on.
Car<Void> car1 = new Car<>(null);
car1.getNewFeature(); //catch Exception here!
And you also have to catch NullPointerException for that. Even thinking with Visitor Design Pattern, its accept(Visitor v); method is always there even if there's no visitor yet.
Advantage: So, is there anything good about it? Yes, there is!
Now, we're gonna create an AbstractFeature class first.
public abstract class AbstractFeature<T> implements Feature<T> {
protected T concreteClass;
@Override
public T getConcreteClass() {
return concreteClass;
}
@Override
public void setConcreteClass(T concreteClass) {
this.concreteClass = concreteClass;
}
}
Now, we're gonna create what kind of new Feature Class we want. Let's say, we wanna add NitrogenGas to the car. Let's create it first. Note: It's a free class. You can add fields as many as you want here. And we'll call this class and use it later.
public class NitrogenGas {
//some methods
public String fillGas() {
return "Filling NitrogenGas";
}
}
And let's add a feature later like this! Just use the AbstractFeature class. That's the reusability.
public class NitrogenGasFeature extends AbstractFeature<NitrogenGas> {
}
Now, you can add a new Feature to the Car class like this! The NitrogenFeature class holds a concrete class we want to use.
Car<NitrogenGas> car2 = new Car<>(new NitrogenGasFeature()); //Feature added
car2.getNewFeature().setConcreteClass(new NitrogenGas()); //Concrete Class added
car2.getNewFeature().getConcreteClass().fillGas(); //use its method.
Let's try and use a Wrapper class like Boolean. Just in case you just need it for backtracking or something.
Car<Boolean> car2 = new Car<>(new BooleanFeature());
car2.getNewFeature().ConcreteClass(new Boolean(true));
System.out.println(car2.getNewFeature().getConcreteClass().booleanValue());
FYI, we can create BacktrackingFeature and Backtracking classes for readability. But it will increase more code. So, use think it wisely.
- This is method 1. It breaks the Single Responsibility, more dependency and more code but increases reusability and readability. Sometimes, when you think too much on the code, even inheritance breaks the Single Responsibility. And that's why we use Composition with the interface. Rules are meant to be broken for the great good.
Method 2 The DTO pattern. You can use like the above answer. Just create a new Object and wrap it. Add any fields or attributes you want to use it. That's the better way. It doesn't break the Single Responsibility Principle. And it has a more meaningful OOP way. But it's up to your decision to make.