7

Consider that we have a Car object. The acceleration and breaking features are implemented using strategy pattern. But what if we want to introduce nitro gas feature to an existing car object ? What is the design pattern that I can use ?

I want to add the nitro feature(Attribute) after creating the car object.

5 Answers 5

11

You can check the Decorator pattern, it can be used to dynamically add functionality to an existing object.

Sign up to request clarification or add additional context in comments.

3 Comments

I love the Decorator pattern.
But can we add new states or behaviors at run time using Decorator pattern.
Decorator pattern is about adding functionality by wrapping the object and exposing an extended interface. This new functionality implementation itself might use additional tools and techniques, such as additional design patterns. Remember, design pattern is not a substitution for your own code, it's a tool that should be used when appropriate.
2

Decorator pattern can add different functionalities to objects dynamically. But these functionalities have to be implemented in a Concrete Decorator. The developer can decide what functionalities to add at run time.

Comments

0

In statically typed languages you can not add methods to an object at runtime. Compiler, when it encounters a statement like: car.nitroAccelerate(), checks whether a car object implements any interface that has nitroAccelerate method. If you could add (or remove) methods during runtime such checks would be impossible.

Dynamic languages allow to add methods during runtime. But this has a drawback, when you put car.nitroAccelerate() in the code, you need to carefully analyze if the car object in this point has such method.

You can use decorator to modify existing methods at runtime, but doing so, you are not modifying an existing object, just creating a new one that wraps the old one.

So if you do something like:

Car fasterCar = new CarWithNitro(car);

and some piece of your code still holds a reference to the original car, this original car would not be faster, because the act of wrapping does not modify the original.

Comments

0

If you want to add new methods, you need to create a new subclass and/or use delegation. This will be necessary if the "nitro" feature requires an explicit method call to activate.

If however all you want to do is to add to existing functionality without adding methods, Decorator is a good bet. Let's say that interface "Car" has a method called floorIt(). In that case, you can add a "nitro kick" to floorIt with Decorator without having to add to the Car inteface.

Of course, there's a middle ground. If you use runtime type discovery and/or multiple interfaces, you can both use Decorator and add methods to the resulting object.

Comments

0

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.

  1. Create a Feature interface.

     public interface Feature<T> {
    
         T getClassValue();
         void setClassValue(T feature);
     }
    
  2. 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!

  1. 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;
         }
     }
    
  2. 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";
         }
     }
    
  3. And let's add a feature later like this! Just use the AbstractFeature class. That's the reusability.

     public class NitrogenGasFeature extends AbstractFeature<NitrogenGas> {
     }
    
  4. 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.
    
  5. 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.

  1. 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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.