CHICIO CODING

Dirty clean code. Creative Stuff. Stuff.

React Native: a simple architecture for Native Modules communication with your UIViewController on iOS

In this post I will talk about a simple architecture for communication between React Native Native modules (aka bridges) and your native code on iOS.


As we saw in a previous post for fragments/Activities Android, sometimes when you integrated React Native in an existing app, you will want to be able let your Native Modules bridges communicate with your UIVIewController, especially the ones that contain the React Native View. In this post I will show you an architecture to put in place this communication on iOS, that will be compatible with all the features of React Native (for example it will work also with the live reload functionality). This is an architecture I put in place for our apps at lastminute.com group. To show this architecture I will create a simple app that show a React Native screen as a modal. I will then implement the close button functionality by calling a native module from the onPress on a React Native button. Below you can see the final result.

The architecture I put in place is based on the NSNotificationCenter. The description of this component of the iOS SDK is the following one:

A notification dispatch mechanism that enables the broadcast of information to registered observers. Objects register with a notification center to receive notifications (NSNotification objects) using the addObserver (_:selector:name:object:) or addObserver(forName:object:queue:using:) methods. When an object adds itself as an observer, it specifies which notifications it should receive. An object may therefore call this method several times in order to register itself as an observer for several different notifications.

This definition basically means that with this api we are able to register class to events send by another one. This is exactly what we need to put in place the communication between our Native Modules bridges and our UIViewController. Let’s start from the MainViewController. In it there’s only a button with an action to start the React Native modal UIViewController called ReactNativeModalViewController.

 class MainViewController: UIViewController {
     override func viewDidLoad() {
         super.viewDidLoad()
     }
     
     @IBAction func showReactNativeModal(_ sender: Any) {
         present(ReactNativeModalViewController(), animated: true, completion: nil)
     }
 }

The ReactNativeModalViewController is a UIViewController with the setup needed to launch a React Native context. This UIViewController is an observer of the ReactEventCloseModal event in the NSNotificationCenter. This event is generated in the Native Modules bridge. The action executed for this event is contained in the closeModal event.

class ReactNativeModalViewController: UIViewController {
    override func viewDidLoad() {
        setupReactNative()
        registerToReactNativeEvents()
    }
    
    private func setupReactNative() {
        let rootView = RCTRootView(
            bundleURL: URL(string: "http://localhost:8081/index.bundle?platform=ios"),
            moduleName: "ReactNativeModal",
            initialProperties: nil,
            launchOptions: nil
        )
        self.view = rootView
    }
    
    private func registerToReactNativeEvents() {
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(closeModal),
                                               name: NSNotification.Name(rawValue: ReactEventCloseModal),
                                               object: nil)
    }
    
    @objc private func closeModal() {
        DispatchQueue.main.async { [unowned self] in
            self.dismiss(animated: true, completion: nil)
        }
    }
}

Now let’s have a look at Native Module created for the app, the ReactNativeModalBridge. In this bridge there just one react method, closeModal. This is the one called from the React Native JS side. In this method we are sending an event with the identifier ReactEventCloseModal. This identifier is defined inside the files ReactNativeEvents .h/ReactNativeEvents.m as a constant with string value closeModal. The ReactNativeModalViewController is subscribed to this type of event (as we saw above). This basically means that when the closeModal bridge method is called from the React Native Javascript code a new event ReactEventCloseModal is generated and the ReactNativeModalViewController will execute the subscribed method defined in it. We have all setup to have our Native Modules communication with our controllers :open_mouth:. Below you can find the header and implementations of the ReactNativeModalBridge bridge (written in Objective-C :sparkling_heart:).

#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>

@interface ReactNativeModalBridge : NSObject<RCTBridgeModule>

@end
  
  
  
#import "ReactNativeModalBridge.h"
#import "ReactNativeEvents.h"

@implementation ReactNativeModalBridge
RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(closeModal) {
    [[NSNotificationCenter defaultCenter] postNotificationName:ReactEventCloseModal object:nil];
}

@end

Now it’s time to see the javascript code. Below you can see the ReactNativeModal component. Inside this component there is a call to the native module NativeModules.ReactNativeModalBridge.closeModal() described above. In this way the modal will be closed directly from the native side.

class ReactNativeModal extends React.Component {
    render() {
        return (
            <View style={styles.container}>
                <Text style={styles.hello}>Hello modal!</Text>
                <Text style={styles.message}>
                    I'm a react native component. Click on the button to close me using native function.
                </Text>
                <Button
                    title={"Close me"}
                    onPress={() => NativeModules.ReactNativeModalBridge.closeModal()}
                />
            </View>
        );
    }
}

That’s all for our native modules communication architecture iOS. You can find the complete example in this github repository. If you want to know how we managed the same problem on the Android platform :rocket: you can read my other post about the same topic.

React Native: a simple architecture for Native Modules communication with your Activities and Fragments on Android

In this post I will talk about a simple architecture for communication between React Native Native modules (aka bridges) and your native code on Android.


Sometimes a React Native app needs to access to the native API or needs/want to call some existing native code you already have in place. This is why Native Modules have been created for both iOS and Android.
Sometimes when you integrated React Native in an existing app, you will want to be able let your Native Modules bridges communicate with your activities and fragment, especially the ones that contain the React Native View . In this post I will show you an architecture to put in place this communication on Android, that will be compatible with all the features of React Native (for example it will work also with the live reload functionality). This is an architecture I put in place with my colleague Felice Giovinazzo in our apps at lastminute.com group. Felice is a senior fullstack developer with many years of experiences (he is the “lastminute” veteran of our team) and a computer graphics enthusiast like me :revolving_hearts::sparkling_heart:.
To show you this architecture I will create a simple app that show a React Native screen as a modal. I will then implement the close button functionality by calling a native module from the onPress on a React Native button. Below you can see the final result.

The architecture we put in place is based on a Event Bus in which the Native Modules bridges notify the subscribed Activities/Fragments of the actions to be executed. So each one of them is subscribed to specific events to which they are able to respond. We choose Otto as event bus library (we don’t want to reinvent the wheel :bomb:). Let’s start from the MainActivity. In it there’s only a button with an action to start the React Native modal activity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void showReactNativeModal(View view) {
        startActivity(new Intent(this, ReactNativeModalActivity.class));
    }
}

The ReactNativeModalActivity is an Activity with the setup needed to launch a React Native context. This activity is registered to the event bus to be able to listen to events from the Native Modules bridges. In this case the activity is subscribed to just one event with the method @Subscribe public void close(ReactNativeModalBridge .CloseModalEvent event).

public class ReactNativeModalActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler {

    private final int OVERLAY_PERMISSION_REQ_CODE = 8762;
    private ReactRootView mReactRootView;
    private ReactInstanceManager mReactInstanceManager;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        registerToReactEvents();
        askReactDrawingPermission();
        setupReactView();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterToReactEvents();

        if (mReactInstanceManager != null) {
            mReactInstanceManager.onHostDestroy(this);
        }
        if (mReactRootView != null) {
            mReactRootView.unmountReactApplication();
        }
    }

    private void registerToReactEvents() {
        ((NativeModulesApplication)getApplication())
                .getBus()
                .register(this);
    }

    private void unregisterToReactEvents() {
        ((NativeModulesApplication)getApplication())
                .getBus()
                .unregister(this);
    }

    @Subscribe
    public void close(ReactNativeModalBridge.CloseModalEvent event) {
        finish();
    }

    private void askReactDrawingPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (!Settings.canDrawOverlays(this)) {
                Intent intent = new Intent(
                        Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                        Uri.parse("package:" + getPackageName())
                );
                startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
            }
        }
    }

    private void setupReactView() {
        ...
    }

    ...
}

Now let’s have a look at Native Module created for the app, the ReactNativeModalBridge. In this bridge there just one react method, closeModal. This is the one called from the React Native JS side. In this method we are sending an event of type CloseModalEvent. The ReactNativeModalActivity is subscribed to this type of event (as we saw above). This basically means that when the closeModal bridge method will be called from the React Native Javascript code a new event CloseModalEvent is generated and the ReactNativeModalActivity will execute the subscribed method defined in it. We have all setup to have our Native Modules communication with our activities (and eventually fragment with the same approach if we need them :neckbeard:).

public class ReactNativeModalBridge extends ReactContextBaseJavaModule {

    public ReactNativeModalBridge(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @Override
    public String getName() {
        return this.getClass().getSimpleName();
    }

    @ReactMethod
    public void closeModal() {
        final Activity currentActivity = getCurrentActivity();
        currentActivity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                ((NativeModulesApplication) currentActivity.getApplication())
                        .getBus()
                        .post(new CloseModalEvent());
            }
        });
    }

    public class CloseModalEvent { }
}

Now it’s time to see the javascript code. Below you can see the ReactNativeModal component. Inside this component there is a call to the native module NativeModules.ReactNativeModalBridge.closeModal() described above. In this way the modal will be closed directly from the native side.

class ReactNativeModal extends React.Component {
    render() {
        return (
            <View style={styles.container}>
                <Text style={styles.hello}>Hello modal!</Text>
                <Text style={styles.message}>
                    I'm a react native component. Click on the button to close me using native function.
                </Text>
                <Button
                    title={"Close me"}
                    onPress={() => NativeModules.ReactNativeModalBridge.closeModal()}
                />
            </View>
        );
    }
}

That’s all for our native modules communication architecture on Android. You can find the complete example in this github repository. If you want to know how we managed the same problem on the iOS platform :apple::iphone::heartbeat: you can read my other post about the same topic.

My first experience as speaker at Voxxed Days 2018: a talk about React, React Native and Typescript

In this post I will talk about my first experience as a speaker at a conference: a talk about React, React Native and Typescript with Alessandro Romano.


In the last few months I talked a lot about React Native and Typescript. In the team where I work at lastminute.com group I acquired a strong knowledge of the Typescript + React + React Native technology stack. There has been also some few changes in the team: Emanuele Ianni, (do you remember I already talked about him in my previous post?), my technical team leader left the company. He was supposed to do a talk at Voxxed 2018 about our journey as a team into the world of React + React Native + Typescript. So I got the opportunity to go in his place to the Voxxed as a co-speaker of Alessandro Romano, my other colleague selected as a speaker for this event. Alessandro, also known as “the Clean”, is a senior software developer with many years of experience that just got graduated from University of Insubria in Varese (do you remember I already talked also about him?).
The title of the talk was: React (Native) & Typescript: A journey to a unified team using a common language. In this post I will talk about all the process we went into from the first draft preparation until the talk :grin:.

Slides preparation

Let’s start from the slides preparation. I started to work on the presentation with Alessandro two months before the event. We decided to structure our talk as a story telling. We begin describing how our team was composed and how we applied agile methodologies: basically, we were divided in three silos, back-end, front-end and mobile doing separated user stories and ceremonies. Then we embraced a new journey, we challenged ourself to become a feature team having end-to-end user stories and unified ceremonies. The technology stack we chose was the main facilitator of this process and eventually transformed us into the mythological creature of the fullstack developer and it was composed of:

  • TypeScript as common language
  • React for the frontend of the customer area (manage all the products of your booking) of our websites
  • React Native for the frontend of the mobile apps of our main brands

After this introduction we created a section for each of the technology above were we described the pros and cons of each one. Last but not least we presented the Cross Selling feature: a real use case in which we were able to share the business logic between the two environments using a pure TypeScript library.

Company dry run, feedback and final present

When the presentation was ready we planned an internal dry run. We usually call this kind of meetings “schiscia time” because they are planned during lunch time: the participants will enjoy their launch while the speakers show their stuff. So we planned our “schiscia time” for the 8th October.
A lot of colleagues attended the talk and gave some very useful feedback. The two major observation we received were:

  • less coding. We created a lot of slides with screenshots taken directly from our IDEs with a lot of code, especially in the section of the presentation where we described the new technology stack we “married”. They were not so easy to read and in some cases they were actually stealing the focus of our attendees. So we decided to remove them. The only slides with code that we kept were the ones in the section “Share the code: cross selling feature” where we present a real use case of development on our products. On this slides we changed the IDEs screenshots with some formatted code using a syntax highlighter (and honestly, after that change the slides looked much more beautiful :heart_eyes:).
  • more focus on the journey. A lot of our colleagues told us that from the presentation they didn’t feel what it took to transform ourselves from platform specific developer to the mythological creature of the fullstack developer. In our presentation there were a lot details about React, React Native and TypeScript but not as much on our workflow with the new technology stack. In fact after choosing React + React Native + TypeScript we started to:
    • do pair programming without considering the technology skills
    • do end-to-end user stories, from the backend service to the frontend (mobile app and web)

So we started to review the slides and we basically created a new presentation :smile:. It took us almost a week. After the review we made 2 simulations of the entire presentation during the week before the event to be more confident. Last but not least, the Human Resources department gave us a company t-shirt to promote our company brand at the conference.

voxxed 2018 tshirt

The talk

Then the day of the talk arrived after a not so quiet sleep. The Voxxed Days 2018 in Ticino was set to take place on 20th of October. We arrived at the Palazzo dei Congressi in Lugano at 8.45 AM. We were excited to queue in the dedicated speakers area for the firt time after so many conferences attended! We checked-in and got our shiny speaker badge. Then we moved to the lounge section to enjoy breakfast.

voxxed 2018 breakfast and badge

Our talk was planned at 2.30 PM, so we had time to attend some other sessions. At 11.50 AM we decided to do a last presentation simulation to review some details and then we went to lunch. At 2.00 PM we started to feel the strain. The start of our talk was really close. We entered in the room of our session at 2.15 PM and we did the setup of our laptop for the presentation.

voxxed 2018 pre talk

Then the room started to fill in. As scheduled at 2.30 PM we started our presentation. The presentation went smooth. We kept the scheduled time per slide we planned in our simulation. The change of speaker between the various part of the presentation worked perfectly. At the end we answered to some questions and we received applause from the audience.

voxxed 2018 pre talk

Conclusion

Tha’s all for my first experience as a conference speaker. It has been a good experience. After 10 years in the IT field (had I already been working for 10 years?!??! :cold_sweat:) it was a great pleasure to be on the other side of the stage.

voxxed 2018 clean

Special thanks to all the team Lynch that give me the opportunity to be the “replacement” speaker for this conference. Special thanks also to Alessandro Romano “the clean”. He is one of the best cospeaker and coworker you could ever find :heart:. If you want to know more about the topic of our presentation, below you can find the full session recorded :bowtie:.

Blender tutorial: outliner, layers, groups, hierarchies and scenes.

In this new post of the series Blender tutorial I will talk about outliner, layers, groups, hierarchies and scenes.


In the previous post of the series “Blender tutorial” we talked about advanced modeling in Blender. In this post we will continue to explore the capabilities of Blender by analyzing outliner, layers, groups, hierarchies and scenes.
Let’s start from the outliner. This one contains the complete list of the objects contained in the scene. Each on of them is represented with its material and geometry. It is possible to rename a mesh by right clicking on it and choosing rename. To the right of each mesh we have the restriction column. It contains 3 buttons:

  • the first button let us hide meshes in the 3D window
  • the second button let us set a mesh as not selectable
  • the third button let us hide meshes from the final renderer

blender outliner

Now let’s talk about layers. We can find the layer panel at the bottom of the 3D window. Each layer is represented with a button similar to a checkbox. We can turn on/off a layer by clicking on their corresponding button. We can add a mesh to a specific layer by selecting the layer and creating the mesh we want (primitive or import it). We can also move layer by using the m key shortcut or by using the menu Object -> Move to layer.

blender layers

Another way to organize our objects is by using groups. With groups you can select multiple objects at once. We can create groups and add an object to an existing group by using the menu in the property panel or by using the ctrl + g keys shortcut.

blender group

After layers and group we have scenes. Scenes allow us to create different set of our objects or new set with new object inside our project. We can create and manage scenes by using the outliner and the ad hoc menu at the top of the 3D window. We can create scenes that:

  • are linked together: this means that a modification to one mesh in a scenes will be reflected also in the other.
  • separated scene: this means that a modification to one mesh in a scenes will NOT be reflected also in the other.

blender scene

Last but not least we have hierarchies. We can create hierarchies of objects by selecting them and by choosing Object -> Parent. Here we can find all the possible type of parental relationships that we can create. After creating a hierarchies of objects, they will be shown grouped in the putliner. We will use hierarchies extensively when we will talk about animation.

blender hierarchies

In the next post we will talk about materials.

Refactoring: a real case of a nested if structure transformed into a chain of responsibility

In this post my colleague Francesco Bonfadelli will show you how to transform a nested if structure into a chain of responsibility.


In this post I am going to describe a step by step process used to transform a nested if structure into a chain of responsibility. The purpose of this operation is to make the code easier to read, thus to change without introducing errors. We will also get for free a structure that will be able to apply a more generic set of rules than the one defined at the beginning.
The code of this post is based on a piece of code used to satisfy a real business need, we just removed the business related details. The language used is java.

The process in short

  • Flatten the if structure into a flat sequence of if clauses
  • Extract each condition and the related action into a single class
  • Create a common interface for all the extracted conditions
  • Put all the conditions into a list
  • Loop over the list and return the first action for which the condition is satisfied

The need

It seemed a normal day of work when one of our managers called a meeting to inform us of a very urgent feature that should be put in production within 2 days. So, as it usually happens in this case, between the deriving chaos and the tons of alignment meetings that continuously interrupted us, we produced a code that basically “worked”, but it was a bit chaotic. Luckily we were able at least to write the tests. So, once we put in production the feature, we decided to immediately refactor the piece of code.

The process

We are going to see a step by step refactor of a specific class that transforms the if-nested structure into a chain of responsibility.

We are not going to change the tests because they work as an acceptance test for our use case. In an ideal world, with a lot of time available, we would also write the tests of all the classes we are going to extract and simplify the current test. But, you know, we are not in an ideal world 😅.
The idea behind this refactor is to proceed with small steps, possibly using the IDE functionality (I used IDEA which is very good at it), and run the tests after every operation. Also, after each step there is a commit, not only to allow everyone to follow the evolution of the code through the commits, but also to allow us to simply use git checkout . in case of errors, in order to come back to the previous working version. All of this, allows us to keep the code strictly under control and avoid to introduce bugs during the refactoring. I will use the diff syntax to show the differences between some pieces of code. Please keep in mind that I will use it in order to highlight only the main differences between one commit and the other and it won’t be the exact diff you can get with git.

The initial code

Here you can find the code we were not very proud of. In particular, I report the nested if structure, which is the part we are going to refactor. (Source code)

public class HandBaggageInformationFactory {

    public HandBaggageInformation from(Order order, TranslationRepository translationRepository, String renderLanguage, Integer flightId) {
        Flight flight = order.findFlight(flightId);
        if (flight.isOneWay()) {
            if (isMyCompany(flight)) {
                LocalDateTime flightOutboundDate = flight.getFirstLeg().getFirstHop().getDeparture().getDate();
                if (flightOutboundDate.isAfter(LocalDateTime.of(2018, 11, 1, 0, 0, 0))) {
                    return newMyCompanyHandBaggageInformation(translationRepository, renderLanguage);
                } else {
                    return oldMyCompanyHandBaggageInformationInfo(translationRepository, renderLanguage);
                }
            } else {
                return noMyCompanyInformationInfo();
            }
        } else { //round trip
            if (isMyCompany(flight)) {
                LocalDateTime outboundDepartureDate = order.getOutboundDepartureDate();
                LocalDate returnDepartureDate = order.getReturnDepartureDate();
                if (outboundDepartureDate.isAfter(LocalDateTime.of(2018, 11, 1, 0, 0, 0))
                        || returnDepartureDate.isAfter(LocalDate.of(2018, 10, 31))) {
                    return newMyCompanyHandBaggageInformation(translationRepository, renderLanguage);
                } else {
                    return oldMyCompanyHandBaggageInformationInfo(translationRepository, renderLanguage);
                }
            } else {
                return noMyCompanyInformationInfo();
            }
        }
    }
}

The execution

1. Flatten the if structure

The idea here is to transform the nested if structure into a flat sequence of if clauses in order to isolate and explicit each single condition.
To do so with very small steps, we are going to remove the else part of each if clause, by transforming such part into an if clause whose condition is the negation of the original one. In the following piece of code, you can notice how the outer if-else has become a couple of if clauses, one for the original condition flight.isOneWay() and the other one with the opposite condition !flight.isOneWay() (Source code)

public class HandBaggageInformationFactory {

    public HandBaggageInformation from(Order order, TranslationRepository translationRepository, String renderLanguage, Integer flightId) {
        Flight flight = order.findFlight(flightId);
        if (flight.isOneWay()) {
            if (isMyCompany(flight)) {
                LocalDateTime flightOutboundDate = flight.getFirstLeg().getFirstHop().getDeparture().getDate();
                if (flightOutboundDate.isAfter(LocalDateTime.of(2018, 11, 1, 0, 0, 0))) {
                    return newMyCompanyHandBaggageInformation(translationRepository, renderLanguage);
                } else {
                    return oldMyCompanyHandBaggageInformationInfo(translationRepository, renderLanguage);
                }
            } else {
                return noMyCompanyInformationInfo();
            }
        }
-       else {
+       if (!flight.isOneWay()) {  //round trip
            if (isMyCompany(flight)) {
                LocalDateTime outboundDepartureDate = order.getOutboundDepartureDate();
                LocalDate returnDepartureDate = order.getReturnDepartureDate();
                if (outboundDepartureDate.isAfter(LocalDateTime.of(2018, 11, 1, 0, 0, 0))
                        || returnDepartureDate.isAfter(LocalDate.of(2018, 10, 31))) {
                    return newMyCompanyHandBaggageInformation(translationRepository, renderLanguage);
                } else {
                    return oldMyCompanyHandBaggageInformationInfo(translationRepository, renderLanguage);
                }
            } else {
                return noMyCompanyInformationInfo();
            }
        }

        return noMyCompanyInformationInfo();
    }
}

Once done this, we are going to proceed with the inner if-else conditions, which is isMyCompany(flight). (Source code)

public class HandBaggageInformationFactory {
    public HandBaggageInformation from(Order order, TranslationRepository translationRepository, String renderLanguage, Integer flightId) {
        Flight flight = order.findFlight(flightId);
        if (flight.isOneWay()) {
            if (isMyCompany(flight)) {
                LocalDateTime flightOutboundDate = flight.getFirstLeg().getFirstHop().getDeparture().getDate();
                if (flightOutboundDate.isAfter(LocalDateTime.of(2018, 11, 1, 0, 0, 0))) {
                    return newMyCompanyHandBaggageInformation(translationRepository, renderLanguage);
                } else {
                    return oldMyCompanyHandBaggageInformationInfo(translationRepository, renderLanguage);
                }
            }
-           else {
+           if (!isMyCompany(flight)) {
                return noMyCompanyInformationInfo();
            }
        }

        if (!flight.isOneWay()) {  //round trip
            if (isMyCompany(flight)) {
                LocalDateTime outboundDepartureDate = order.getOutboundDepartureDate();
                LocalDate returnDepartureDate = order.getReturnDepartureDate();
                if (outboundDepartureDate.isAfter(LocalDateTime.of(2018, 11, 1, 0, 0, 0))
                        || returnDepartureDate.isAfter(LocalDate.of(2018, 10, 31))) {
                    return newMyCompanyHandBaggageInformation(translationRepository, renderLanguage);
                } else {
                    return oldMyCompanyHandBaggageInformationInfo(translationRepository, renderLanguage);
                }
            }

            if (!isMyCompany(flight)) {
                return noMyCompanyInformationInfo();
            }
        }

        return noMyCompanyInformationInfo();
    }
}

We proceed in this way until we have removed all the else conditions from the code. Here, you are not forced to start from the most external clause, but you can choose whatever position you prefer to start with. The important thing is that once finished you won’t have any else clause inside your code. (Source code)

public class HandBaggageInformationFactory {
    public HandBaggageInformation from(Order order, TranslationRepository translationRepository, String renderLanguage, Integer flightId) {
        Flight flight = order.findFlight(flightId);
        if (flight.isOneWay()) {
            if (isMyCompany(flight)) {
                LocalDateTime flightOutboundDate = flight.getFirstLeg().getFirstHop().getDeparture().getDate();
                if (flightOutboundDate.isAfter(LocalDateTime.of(2018, 11, 1, 0, 0, 0))) {
                    return newMyCompanyHandBaggageInformation(translationRepository, renderLanguage);
                }

                if (!flightOutboundDate.isAfter(LocalDateTime.of(2018, 11, 1, 0, 0, 0))) {
                    return oldMyCompanyHandBaggageInformationInfo(translationRepository, renderLanguage);
                }
            }

            if (!isMyCompany(flight)) {
                return noMyCompanyInformationInfo();
            }
        }

        if (!flight.isOneWay()) {  //round trip
            if (isMyCompany(flight)) {
                LocalDateTime outboundDepartureDate = order.getOutboundDepartureDate();
                LocalDate returnDepartureDate = order.getReturnDepartureDate();
                if (outboundDepartureDate.isAfter(LocalDateTime.of(2018, 11, 1, 0, 0, 0))
                        || returnDepartureDate.isAfter(LocalDate.of(2018, 10, 31))) {
                    return newMyCompanyHandBaggageInformation(translationRepository, renderLanguage);
                }

                if (!(outboundDepartureDate.isAfter(LocalDateTime.of(2018, 11, 1, 0, 0, 0))
                        || returnDepartureDate.isAfter(LocalDate.of(2018, 10, 31)))) {
                    return oldMyCompanyHandBaggageInformationInfo(translationRepository, renderLanguage);
                }
            }

            if (!isMyCompany(flight)) {
                return noMyCompanyInformationInfo();
            }
        }

        return noMyCompanyInformationInfo();
    }
}

Once removed all the else, we are going to duplicate some conditions in order to have only one if clause inside another if. At a first glance, it could seem complicated to understand but it is actually pretty simple 🚀. We start by duplicating isMyCompany(flight) in the two external if clauses. (Source code)

public class HandBaggageInformationFactory {
    public HandBaggageInformation from(Order order, TranslationRepository translationRepository, String renderLanguage, Integer flightId) {
        Flight flight = order.findFlight(flightId);
        if (flight.isOneWay()) {
            LocalDateTime flightOutboundDate = flight.getFirstLeg().getFirstHop().getDeparture().getDate();
            if (isMyCompany(flight)) {
                if (flightOutboundDate.isAfter(LocalDateTime.of(2018, 11, 1, 0, 0, 0))) {
                    return newMyCompanyHandBaggageInformation(translationRepository, renderLanguage);
                }

-               if (!flightOutboundDate.isAfter(LocalDateTime.of(2018, 11, 1, 0, 0, 0))) {
-                   return oldMyCompanyHandBaggageInformationInfo(translationRepository, renderLanguage);
-               }
            }
            
+           if (isMyCompany(flight)) {
+               if (!flightOutboundDate.isAfter(LocalDateTime.of(2018, 11, 1, 0, 0, 0))) {
+                   return oldMyCompanyHandBaggageInformationInfo(translationRepository, renderLanguage);
+               }
+           }

            if (!isMyCompany(flight)) {
                return noMyCompanyInformationInfo();
            }
        }

        if (!flight.isOneWay()) {  //round trip
            LocalDateTime outboundDepartureDate = order.getOutboundDepartureDate();
            LocalDate returnDepartureDate = order.getReturnDepartureDate();
            if (isMyCompany(flight)) {
                if (outboundDepartureDate.isAfter(LocalDateTime.of(2018, 11, 1, 0, 0, 0))
                        || returnDepartureDate.isAfter(LocalDate.of(2018, 10, 31))) {
                    return newMyCompanyHandBaggageInformation(translationRepository, renderLanguage);
                }
                
-               if (!(outboundDepartureDate.isAfter(LocalDateTime.of(2018, 11, 1, 0, 0, 0))
-                       || returnDepartureDate.isAfter(LocalDate.of(2018, 10, 31)))) {
-                   return oldMyCompanyHandBaggageInformationInfo(translationRepository, renderLanguage);
-               }

            }
            
+           if (isMyCompany(flight)) {
+               if (!(outboundDepartureDate.isAfter(LocalDateTime.of(2018, 11, 1, 0, 0, 0))
+                       || returnDepartureDate.isAfter(LocalDate.of(2018, 10, 31)))) {
+                   return oldMyCompanyHandBaggageInformationInfo(translationRepository, renderLanguage);
+               }
+           }

            if (!isMyCompany(flight)) {
                return noMyCompanyInformationInfo();
            }
        }

        return noMyCompanyInformationInfo();
    }
}

After having done this process for all the conditions, we will finally get the flat if structure. (Source code)

public class HandBaggageInformationFactory {
    private static final LocalDateTime FIRST_OF_NOVEMBER = LocalDateTime.of(2018, 11, 1, 0, 0, 0);
    private static final LocalDate THIRTY_FIRST_OF_OCTOBER = LocalDate.of(2018, 10, 31);

    public HandBaggageInformation from(Order order, TranslationRepository translationRepository, String renderLanguage, Integer flightId) {
        Flight flight = order.findFlight(flightId);
        LocalDateTime flightOutboundDate = flight.getFirstLeg().getFirstHop().getDeparture().getDate();
        LocalDateTime outboundDepartureDate = order.getOutboundDepartureDate();
        LocalDate returnDepartureDate = order.getReturnDepartureDate();

        if (flight.isOneWay()
                && isMyCompany(flight)
                && flightOutboundDate.isAfter(FIRST_OF_NOVEMBER)) {
            return newMyCompanyHandBaggageInformation(translationRepository, renderLanguage);
        }

        if (flight.isOneWay() && isMyCompany(flight) && !flightOutboundDate.isAfter(FIRST_OF_NOVEMBER)) {
            return oldMyCompanyHandBaggageInformationInfo(translationRepository, renderLanguage);
        }

        if (flight.isOneWay() && !isMyCompany(flight)) {
                return noMyCompanyInformationInfo();
        }

        if (!flight.isOneWay() && isMyCompany(flight) && (outboundDepartureDate.isAfter(FIRST_OF_NOVEMBER)
                        || returnDepartureDate.isAfter(THIRTY_FIRST_OF_OCTOBER))) {
                    return newMyCompanyHandBaggageInformation(translationRepository, renderLanguage);
        }

        if (!flight.isOneWay() && isMyCompany(flight) && (!(outboundDepartureDate.isAfter(FIRST_OF_NOVEMBER)
                        || returnDepartureDate.isAfter(THIRTY_FIRST_OF_OCTOBER)))) {
                    return oldMyCompanyHandBaggageInformationInfo(translationRepository, renderLanguage);
        }

        if (!flight.isOneWay() && !isMyCompany(flight)) {
                return noMyCompanyInformationInfo();
        }

        return noMyCompanyInformationInfo();
    }
}

Intermediate step: extracting factories

Before keeping on with the extraction of the chain of responsibility from the if structure, we are going to make some intermediate steps. In order to reduce the responsibilities of the HandBaggageInformationFactory, here, we are going to extract three factories, each one responsible for creating a specific HandBaggageInformation. Without diving into the code used to create the object, we just extract the NewMyCompanyHandBaggageInformationFactory out of the method newMyCompanyHandBaggageInformation. (Step 1, Step 2 and Step 3) If you are using IDEA, an easy way is to do it is to use its Extract method object feature. I won’t explain how to do it here, because it is out of the scope of this topic, but I have just realized I have found the next topic of my blog (this is great! isn’t it? :smirk:).

public class HandBaggageInformationFactory {
    public HandBaggageInformation from(Order order, TranslationRepository translationRepository, String renderLanguage, Integer flightId) {
        Flight flight = order.findFlight(flightId);
        LocalDateTime flightOutboundDate = flight.getFirstLeg().getFirstHop().getDeparture().getDate();
        LocalDateTime outboundDepartureDate = order.getOutboundDepartureDate();
        LocalDate returnDepartureDate = order.getReturnDepartureDate();

+        NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory =
+                new NewMyCompanyHandBaggageInformationFactory(translationRepository);

        if (flight.isOneWay()
                && isMyCompany(flight)
                && flightOutboundDate.isAfter(FIRST_OF_NOVEMBER)) {
-             return newMyCompanyHandBaggageInformation(translationRepository, renderLanguage);
+            return newMyCompanyHandBaggageInformationFactory.execute(renderLanguage);
        }

        if (flight.isOneWay() && isMyCompany(flight) && !flightOutboundDate.isAfter(FIRST_OF_NOVEMBER)) {
            return oldMyCompanyHandBaggageInformationInfo(translationRepository, renderLanguage);
        }

        if (flight.isOneWay() && !isMyCompany(flight)) {
                return noMyCompanyInformationInfo();
        }

        if (!flight.isOneWay() && isMyCompany(flight) && (outboundDepartureDate.isAfter(FIRST_OF_NOVEMBER)
                        || returnDepartureDate.isAfter(THIRTY_FIRST_OF_OCTOBER))) {
-           return newMyCompanyHandBaggageInformation(translationRepository, renderLanguage);
+           return newMyCompanyHandBaggageInformationFactory.execute(renderLanguage);
        }

        if (!flight.isOneWay() && isMyCompany(flight) && (!(outboundDepartureDate.isAfter(FIRST_OF_NOVEMBER)
                        || returnDepartureDate.isAfter(THIRTY_FIRST_OF_OCTOBER)))) {
                    return oldMyCompanyHandBaggageInformationInfo(translationRepository, renderLanguage);
        }

        if (!flight.isOneWay() && !isMyCompany(flight)) {
                return noMyCompanyInformationInfo();
        }

        return noMyCompanyInformationInfo();
    }
}

Once done this, we repeat the operation for the other two methods that create the objects, obtaining the NotMyCompanyHandBaggageInformationFactory and the OldMyCompanyHandBaggageInformationFactory. (Source code)

public class HandBaggageInformationFactory {
    public HandBaggageInformation from(Order order, TranslationRepository translationRepository, String renderLanguage, Integer flightId) {
        Flight flight = order.findFlight(flightId);
        LocalDateTime flightOutboundDate = flight.getFirstLeg().getFirstHop().getDeparture().getDate();
        LocalDateTime outboundDepartureDate = order.getOutboundDepartureDate();
        LocalDate returnDepartureDate = order.getReturnDepartureDate();

        NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory =
                new NewMyCompanyHandBaggageInformationFactory(translationRepository);
+       OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory =
+               new OldMyCompanyHandBaggageInformationFactory(translationRepository);
+       NotMyCompanyHandBaggageInformationFactory notMyCompanyHandBaggageInformationFactory =
+               new NotMyCompanyHandBaggageInformationFactory();

        if (flight.isOneWay()
                && isMyCompany(flight)
                && flightOutboundDate.isAfter(FIRST_OF_NOVEMBER)) {
              return newMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }

        if (flight.isOneWay() && isMyCompany(flight) && !flightOutboundDate.isAfter(FIRST_OF_NOVEMBER)) {
-            return oldMyCompanyHandBaggageInformationInfo(translationRepository, renderLanguage);        
+            return oldMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }

        if (flight.isOneWay() && !isMyCompany(flight)) {
-            return noMyCompanyInformationInfo();        
+            return notMyCompanyHandBaggageInformationFactory.make();
        }

        if (!flight.isOneWay() && isMyCompany(flight) && (outboundDepartureDate.isAfter(FIRST_OF_NOVEMBER)
                        || returnDepartureDate.isAfter(THIRTY_FIRST_OF_OCTOBER))) {
             return newMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }

        if (!flight.isOneWay() && isMyCompany(flight) && (!(outboundDepartureDate.isAfter(FIRST_OF_NOVEMBER)
                        || returnDepartureDate.isAfter(THIRTY_FIRST_OF_OCTOBER)))) {
-            return oldMyCompanyHandBaggageInformationInfo(translationRepository, renderLanguage);
+            return oldMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }

        if (!flight.isOneWay() && !isMyCompany(flight)) {
-            return noMyCompanyInformationInfo();
+            return notMyCompanyHandBaggageInformationFactory.make();
        }

-       return noMyCompanyInformationInfo();
+       return notMyCompanyHandBaggageInformationFactory.make();
    }
}

2. Creating the components of the chain

By using again the Extract method object feature of Idea, you can easily extract the first condition into a class. In this way we get new MyCompanyOneWayAfterTheFirstOfNovember().canHandle(flight, flightOutboundDate) in the first if condition. (Source code)

public class HandBaggageInformationFactory {
    public HandBaggageInformation from(Order order, TranslationRepository translationRepository, String renderLanguage, Integer flightId) {
        Flight flight = order.findFlight(flightId);
        LocalDateTime flightOutboundDate = flight.getFirstLeg().getFirstHop().getDeparture().getDate();
        LocalDateTime outboundDepartureDate = order.getOutboundDepartureDate();
        LocalDate returnDepartureDate = order.getReturnDepartureDate();

        NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory =
                new NewMyCompanyHandBaggageInformationFactory(translationRepository);
        OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory =
                new OldMyCompanyHandBaggageInformationFactory(translationRepository);
        NotMyCompanyHandBaggageInformationFactory notMyCompanyHandBaggageInformationFactory =
                new NotMyCompanyHandBaggageInformationFactory();

-       if (flight.isOneWay()
-               && isMyCompany(flight)
-               && flightOutboundDate.isAfter(FIRST_OF_NOVEMBER)) {
-             return newMyCompanyHandBaggageInformationFactory.from(renderLanguage);
-       }
        
+       if (new MyCompanyOneWayAfterTheFirstOfNovember().canHandle(flight, flightOutboundDate)) { 
+           return newMyCompanyHandBaggageInformationFactory.from(renderLanguage);
+       }

        if (flight.isOneWay() && isMyCompany(flight) && !flightOutboundDate.isAfter(FIRST_OF_NOVEMBER)) {
            return oldMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }

        if (flight.isOneWay() && !isMyCompany(flight)) {
            return notMyCompanyHandBaggageInformationFactory.make();
        }

        if (!flight.isOneWay() && isMyCompany(flight) && (outboundDepartureDate.isAfter(FIRST_OF_NOVEMBER)
                        || returnDepartureDate.isAfter(THIRTY_FIRST_OF_OCTOBER))) {
            return newMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }

        if (!flight.isOneWay() && isMyCompany(flight) && (!(outboundDepartureDate.isAfter(FIRST_OF_NOVEMBER)
                        || returnDepartureDate.isAfter(THIRTY_FIRST_OF_OCTOBER)))) {
            return oldMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }

        if (!flight.isOneWay() && !isMyCompany(flight)) {
            return notMyCompanyHandBaggageInformationFactory.make();
        }

        return notMyCompanyHandBaggageInformationFactory.make();
    }

+   private class MyCompanyOneWayAfterTheFirstOfNovember {
+       public boolean canHandle(Flight flight, LocalDateTime flightOutboundDate) {
+           return flight.isOneWay()
+                   && isMyCompany(flight)
+                   && flightOutboundDate.isAfter(FIRST_OF_NOVEMBER);
+       }
+   }
}

And, after that, we can move newMyCompanyHandBaggageInformationFactory.from(renderLanguage) inside MyCompanyOneWayAfterTheFirstOfNovember.

public class HandBaggageInformationFactory {
    public HandBaggageInformation from(Order order, TranslationRepository translationRepository, String renderLanguage, Integer flightId) {
        Flight flight = order.findFlight(flightId);
        LocalDateTime flightOutboundDate = flight.getFirstLeg().getFirstHop().getDeparture().getDate();
        LocalDateTime outboundDepartureDate = order.getOutboundDepartureDate();
        LocalDate returnDepartureDate = order.getReturnDepartureDate();

        NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory =
                new NewMyCompanyHandBaggageInformationFactory(translationRepository);
        OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory =
                new OldMyCompanyHandBaggageInformationFactory(translationRepository);
        NotMyCompanyHandBaggageInformationFactory notMyCompanyHandBaggageInformationFactory =
                new NotMyCompanyHandBaggageInformationFactory();

-       if (new MyCompanyOneWayAfterTheFirstOfNovember().canHandle(flight, flightOutboundDate)) { 
-           return newMyCompanyHandBaggageInformationFactory.from(renderLanguage);
-       }
+        MyCompanyOneWayAfterTheFirstOfNovember myCompanyOneWayAfterTheFirstOfNovember =
+                        new MyCompanyOneWayAfterTheFirstOfNovember(newMyCompanyHandBaggageInformationFactory);
+                if (myCompanyOneWayAfterTheFirstOfNovember.canHandle(flight, flightOutboundDate)) {
+                    return myCompanyOneWayAfterTheFirstOfNovember.getFrom(renderLanguage);
+                }

        if (flight.isOneWay() && isMyCompany(flight) && !flightOutboundDate.isAfter(FIRST_OF_NOVEMBER)) {
            return oldMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }

        if (flight.isOneWay() && !isMyCompany(flight)) {
            return notMyCompanyHandBaggageInformationFactory.make();
        }

        if (!flight.isOneWay() && isMyCompany(flight) && (outboundDepartureDate.isAfter(FIRST_OF_NOVEMBER)
                        || returnDepartureDate.isAfter(THIRTY_FIRST_OF_OCTOBER))) {
            return newMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }

        if (!flight.isOneWay() && isMyCompany(flight) && (!(outboundDepartureDate.isAfter(FIRST_OF_NOVEMBER)
                        || returnDepartureDate.isAfter(THIRTY_FIRST_OF_OCTOBER)))) {
            return oldMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }

        if (!flight.isOneWay() && !isMyCompany(flight)) {
            return notMyCompanyHandBaggageInformationFactory.make();
        }

        return notMyCompanyHandBaggageInformationFactory.make();
    }

    private class MyCompanyOneWayAfterTheFirstOfNovember {
    
        private final NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory;
    
        private MyCompanyOneWayAfterTheFirstOfNovember(NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory) {
            this.newMyCompanyHandBaggageInformationFactory = newMyCompanyHandBaggageInformationFactory;
        }
        
        public boolean canHandle(Flight flight, LocalDateTime flightOutboundDate) {
            return flight.isOneWay()
                        && isMyCompany(flight)
                        && flightOutboundDate.isAfter(FIRST_OF_NOVEMBER);
        }
    
+       public HandBaggageInformation getFrom(String renderLanguage) {
+               return this.newMyCompanyHandBaggageInformationFactory.from(renderLanguage);
+       }
    }
}

Then, we repeat the operation for all the conditions except for the ones that use our default value, notMyCompanyHandBaggageInformationFactory.make(). First of all, we remove the condition that once true uses the default behaviour (!flight.isOneWay() && !isMyCompany(flight)), because it is redundant. Then, for each remaining condition we create a class containing the evaluation of the condition and the related action.
I skip this step by step diff because it is a repetition of the previous one, but in the repo there are all the commits that show the process. The resulting code, after having extracted all the conditions, is the following. (Source code)

public class HandBaggageInformationFactory {
    public HandBaggageInformation from(Order order, TranslationRepository translationRepository, String renderLanguage, Integer flightId) {
        Flight flight = order.findFlight(flightId);
        LocalDateTime flightOutboundDate = flight.getFirstLeg().getFirstHop().getDeparture().getDate();
        LocalDateTime outboundDepartureDate = order.getOutboundDepartureDate();
        LocalDate returnDepartureDate = order.getReturnDepartureDate();

        NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory =
                new NewMyCompanyHandBaggageInformationFactory(translationRepository);
        OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory =
                new OldMyCompanyHandBaggageInformationFactory(translationRepository);
        NotMyCompanyHandBaggageInformationFactory notMyCompanyHandBaggageInformationFactory =
                new NotMyCompanyHandBaggageInformationFactory();

        MyCompanyOneWayAfterTheFirstOfNovember myCompanyOneWayAfterTheFirstOfNovember =
                new MyCompanyOneWayAfterTheFirstOfNovember(newMyCompanyHandBaggageInformationFactory);
        if (myCompanyOneWayAfterTheFirstOfNovember.canHandle(flight, flightOutboundDate)) {
            return myCompanyOneWayAfterTheFirstOfNovember.getFrom(renderLanguage);
        }

        MyCompanyOneWayBeforeTheFirstOfNovember myCompanyOneWayBeforeTheFirstOfNovember =
                new MyCompanyOneWayBeforeTheFirstOfNovember(oldMyCompanyHandBaggageInformationFactory);
        if (myCompanyOneWayBeforeTheFirstOfNovember.canHandle(flight, flightOutboundDate)) {
            return myCompanyOneWayBeforeTheFirstOfNovember.getFrom(renderLanguage);
        }

        MyCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember myCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember =
                new MyCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember(newMyCompanyHandBaggageInformationFactory);
        if (myCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember.canHandle(flight, outboundDepartureDate, returnDepartureDate)) {
            return myCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember
                    .getFrom(renderLanguage);
        }

        MyCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember myCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember = new
                MyCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember(oldMyCompanyHandBaggageInformationFactory);
        if (myCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember.canHandle(flight, outboundDepartureDate, returnDepartureDate)) {
            return myCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember.getFrom(renderLanguage);
        }

        return notMyCompanyHandBaggageInformationFactory.make();
    }

    private class MyCompanyOneWayAfterTheFirstOfNovember {

        private final NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory;

        private MyCompanyOneWayAfterTheFirstOfNovember(NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory) {
            this.newMyCompanyHandBaggageInformationFactory = newMyCompanyHandBaggageInformationFactory;
        }

        public boolean canHandle(Flight flight, LocalDateTime flightOutboundDate) {
            return flight.isOneWay()
                    && isMyCompany(flight)
                    && flightOutboundDate.isAfter(FIRST_OF_NOVEMBER);
        }

        public HandBaggageInformation getFrom(String renderLanguage) {
            return this.newMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }
    }

    private class MyCompanyOneWayBeforeTheFirstOfNovember {
        private final OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory;

        private MyCompanyOneWayBeforeTheFirstOfNovember(OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory) {
            this.oldMyCompanyHandBaggageInformationFactory = oldMyCompanyHandBaggageInformationFactory;
        }

        public boolean canHandle(Flight flight, LocalDateTime flightOutboundDate) {
            return flight.isOneWay() && isMyCompany(flight) && !flightOutboundDate.isAfter(FIRST_OF_NOVEMBER);
        }

        private HandBaggageInformation getFrom(String renderLanguage) {
            return this.oldMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }
    }

    private class MyCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember {
        private final NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory;

        public MyCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember(NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory) {
            this.newMyCompanyHandBaggageInformationFactory = newMyCompanyHandBaggageInformationFactory;
        }

        public boolean canHandle(Flight flight, LocalDateTime outboundDepartureDate, LocalDate returnDepartureDate) {
            return !flight.isOneWay() && isMyCompany(flight) && (outboundDepartureDate.isAfter(FIRST_OF_NOVEMBER)
                    || returnDepartureDate.isAfter(THIRTY_FIRST_OF_OCTOBER));
        }

        public HandBaggageInformation getFrom(String renderLanguage) {
            return this.newMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }
    }

    private class MyCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember {
        private final OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory;

        private MyCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember(OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory) {
            this.oldMyCompanyHandBaggageInformationFactory = oldMyCompanyHandBaggageInformationFactory;
        }

        private boolean canHandle(Flight flight, LocalDateTime outboundDepartureDate, LocalDate returnDepartureDate) {
            return !flight.isOneWay() && isMyCompany(flight) && (!(outboundDepartureDate.isAfter(FIRST_OF_NOVEMBER)
                    || returnDepartureDate.isAfter(THIRTY_FIRST_OF_OCTOBER)));
        }

        public HandBaggageInformation getFrom(String renderLanguage) {
            return oldMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }
    }
}

3. Create a common signature to extract the chain item

The purpose here is to obtain a common signature for all the conditions we have just extracted.
As this part is more domain oriented, I am not diving into the details.
Just notice the three main modifications:

  • the Order has the same information as the Flight for our case, so we remove the Order class in favour of the Flight class.
  • Keep only one date, which is our threshold date, by playing a bit with isAfter() and isBefore().
  • Move some operations from the HandBaggageInformationFactory class to the Flight one, which is more domain oriented. (Source code)

So, after these operations the code looks like

public class HandBaggageInformationFactory {
    private static final LocalDateTime FIRST_OF_NOVEMBER = LocalDateTime.of(2018, 11, 1, 0, 0, 0);

    public HandBaggageInformation from(Order order, TranslationRepository translationRepository, String renderLanguage, Integer flightId) {
        Flight flight = order.findFlight(flightId);

        NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory =
                new NewMyCompanyHandBaggageInformationFactory(translationRepository);
        OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory =
                new OldMyCompanyHandBaggageInformationFactory(translationRepository);
        NotMyCompanyHandBaggageInformationFactory notMyCompanyHandBaggageInformationFactory =
                new NotMyCompanyHandBaggageInformationFactory();

        MyCompanyOneWayAfterTheFirstOfNovember myCompanyOneWayAfterTheFirstOfNovember =
                new MyCompanyOneWayAfterTheFirstOfNovember(newMyCompanyHandBaggageInformationFactory);
        if (myCompanyOneWayAfterTheFirstOfNovember.canHandle(flight)) {
            return myCompanyOneWayAfterTheFirstOfNovember.getFrom(renderLanguage);
        }

        MyCompanyOneWayBeforeTheFirstOfNovember myCompanyOneWayBeforeTheFirstOfNovember =
                new MyCompanyOneWayBeforeTheFirstOfNovember(oldMyCompanyHandBaggageInformationFactory);
        if (myCompanyOneWayBeforeTheFirstOfNovember.canHandle(flight)) {
            return myCompanyOneWayBeforeTheFirstOfNovember.getFrom(renderLanguage);
        }

        MyCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember myCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember =
                new MyCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember(newMyCompanyHandBaggageInformationFactory);
        if (myCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember.canHandle(flight)) {
            return myCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember
                    .getFrom(renderLanguage);
        }

        MyCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember myCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember = new
                MyCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember(oldMyCompanyHandBaggageInformationFactory);
        if (myCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember.canHandle(flight)) {
            return myCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember.getFrom(renderLanguage);
        }

        return notMyCompanyHandBaggageInformationFactory.make();
    }


    private class MyCompanyOneWayAfterTheFirstOfNovember {

        private final NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory;

        private MyCompanyOneWayAfterTheFirstOfNovember(NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory) {
            this.newMyCompanyHandBaggageInformationFactory = newMyCompanyHandBaggageInformationFactory;
        }

        public boolean canHandle(Flight flight) {
            return flight.isOneWay()
                    && flight.isMyCompany()
                    && flight.getOutboundDepartureDate().isAfter(FIRST_OF_NOVEMBER);
        }

        public HandBaggageInformation getFrom(String renderLanguage) {
            return this.newMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }
    }

    private class MyCompanyOneWayBeforeTheFirstOfNovember {
        private final OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory;

        private MyCompanyOneWayBeforeTheFirstOfNovember(OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory) {
            this.oldMyCompanyHandBaggageInformationFactory = oldMyCompanyHandBaggageInformationFactory;
        }

        public boolean canHandle(Flight flight) {
            return flight.isOneWay()
                    && flight.isMyCompany()
                    && !flight.getOutboundDepartureDate().isAfter(FIRST_OF_NOVEMBER);
        }

        private HandBaggageInformation getFrom(String renderLanguage) {
            return this.oldMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }
    }

    private class MyCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember {
        private final NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory;

        public MyCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember(NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory) {
            this.newMyCompanyHandBaggageInformationFactory = newMyCompanyHandBaggageInformationFactory;
        }

        public boolean canHandle(Flight flight) {
            return !flight.isOneWay()
                    && flight.isMyCompany()
                    && (flight.getOutboundDepartureDate().isAfter(FIRST_OF_NOVEMBER)
                        || flight.getReturnDepartureDate().isAfter(FIRST_OF_NOVEMBER)
                    );
        }

        public HandBaggageInformation getFrom(String renderLanguage) {
            return this.newMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }
    }

    private class MyCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember {
        private final OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory;

        private MyCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember(OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory) {
            this.oldMyCompanyHandBaggageInformationFactory = oldMyCompanyHandBaggageInformationFactory;
        }

        private boolean canHandle(Flight flight) {
            return !flight.isOneWay()
                    && flight.isMyCompany()
                    && (!(flight.getOutboundDepartureDate().isAfter(FIRST_OF_NOVEMBER)
                        || flight.getReturnDepartureDate().isAfter(FIRST_OF_NOVEMBER))
                    );
        }

        public HandBaggageInformation getFrom(String renderLanguage) {
            return oldMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }
    }
}

4. Extracting the interface of the chain item

By watching closely all the extracted conditions after the simplifications made, you can notice that now all the items has a common method signature. And if you think that is time of an interface, you are totally right 🏆. So, we can easily extract an interface from one conditions chosen randomly, for example MyCompanyOneWayAfterTheFirstOfNovember. (Source code)

If you use Idea, its Extract interface feature can be helpful.

+   public interface HandBaggageInformationPolicy {
+       boolean canHandle(Flight flight);
+       HandBaggageInformation getFrom(String renderLanguage);
+   }


public class HandBaggageInformationFactory {
    private static final LocalDateTime FIRST_OF_NOVEMBER = LocalDateTime.of(2018, 11, 1, 0, 0, 0);

    public HandBaggageInformation from(Order order, TranslationRepository translationRepository, String renderLanguage, Integer flightId) {
        Flight flight = order.findFlight(flightId);

        NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory =
                new NewMyCompanyHandBaggageInformationFactory(translationRepository);
        OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory =
                new OldMyCompanyHandBaggageInformationFactory(translationRepository);
        NotMyCompanyHandBaggageInformationFactory notMyCompanyHandBaggageInformationFactory =
                new NotMyCompanyHandBaggageInformationFactory();

        MyCompanyOneWayAfterTheFirstOfNovember myCompanyOneWayAfterTheFirstOfNovember =
                new MyCompanyOneWayAfterTheFirstOfNovember(newMyCompanyHandBaggageInformationFactory);
        if (myCompanyOneWayAfterTheFirstOfNovember.canHandle(flight)) {
            return myCompanyOneWayAfterTheFirstOfNovember.getFrom(renderLanguage);
        }

        MyCompanyOneWayBeforeTheFirstOfNovember myCompanyOneWayBeforeTheFirstOfNovember =
                new MyCompanyOneWayBeforeTheFirstOfNovember(oldMyCompanyHandBaggageInformationFactory);
        if (myCompanyOneWayBeforeTheFirstOfNovember.canHandle(flight)) {
            return myCompanyOneWayBeforeTheFirstOfNovember.getFrom(renderLanguage);
        }

        MyCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember myCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember =
                new MyCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember(newMyCompanyHandBaggageInformationFactory);
        if (myCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember.canHandle(flight)) {
            return myCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember
                    .getFrom(renderLanguage);
        }

        MyCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember myCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember = new
                MyCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember(oldMyCompanyHandBaggageInformationFactory);
        if (myCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember.canHandle(flight)) {
            return myCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember.getFrom(renderLanguage);
        }

        return notMyCompanyHandBaggageInformationFactory.make();
    }

-   private class MyCompanyOneWayAfterTheFirstOfNovember {
+   private class MyCompanyOneWayAfterTheFirstOfNovember implements HandBaggageInformationPolicy {

        private final NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory;

        private MyCompanyOneWayAfterTheFirstOfNovember(NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory) {
            this.newMyCompanyHandBaggageInformationFactory = newMyCompanyHandBaggageInformationFactory;
        }

+       @Override
        public boolean canHandle(Flight flight) {
            return flight.isOneWay()
                    && flight.isMyCompany()
                    && flight.getOutboundDepartureDate().isAfter(FIRST_OF_NOVEMBER);
        }

+       @Override
        public HandBaggageInformation getFrom(String renderLanguage) {
            return this.newMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }
    }

    private class MyCompanyOneWayBeforeTheFirstOfNovember {
        private final OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory;

        private MyCompanyOneWayBeforeTheFirstOfNovember(OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory) {
            this.oldMyCompanyHandBaggageInformationFactory = oldMyCompanyHandBaggageInformationFactory;
        }

        public boolean canHandle(Flight flight) {
            return flight.isOneWay()
                    && flight.isMyCompany()
                    && !flight.getOutboundDepartureDate().isAfter(FIRST_OF_NOVEMBER);
        }

        private HandBaggageInformation getFrom(String renderLanguage) {
            return this.oldMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }
    }

    private class MyCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember {
        private final NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory;

        public MyCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember(NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory) {
            this.newMyCompanyHandBaggageInformationFactory = newMyCompanyHandBaggageInformationFactory;
        }

        public boolean canHandle(Flight flight) {
            return !flight.isOneWay()
                    && flight.isMyCompany()
                    && (flight.getOutboundDepartureDate().isAfter(FIRST_OF_NOVEMBER)
                        || flight.getReturnDepartureDate().isAfter(FIRST_OF_NOVEMBER)
                    );
        }

        public HandBaggageInformation getFrom(String renderLanguage) {
            return this.newMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }
    }

    private class MyCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember {
        private final OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory;

        private MyCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember(OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory) {
            this.oldMyCompanyHandBaggageInformationFactory = oldMyCompanyHandBaggageInformationFactory;
        }

        private boolean canHandle(Flight flight) {
            return !flight.isOneWay()
                    && flight.isMyCompany()
                    && (!(flight.getOutboundDepartureDate().isAfter(FIRST_OF_NOVEMBER)
                        || flight.getReturnDepartureDate().isAfter(FIRST_OF_NOVEMBER))
                    );
        }

        public HandBaggageInformation getFrom(String renderLanguage) {
            return oldMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }
    }
}

And then, we are going to make all the conditions implement the interface HandBaggageInformationPolicy. (Source code)

Unfortunately Idea won’t help us in this.

public class HandBaggageInformationFactory {
    private static final LocalDateTime FIRST_OF_NOVEMBER = LocalDateTime.of(2018, 11, 1, 0, 0, 0);

    public HandBaggageInformation from(Order order, TranslationRepository translationRepository, String renderLanguage, Integer flightId) {
        //same as before
    }


    private class MyCompanyOneWayAfterTheFirstOfNovember implements HandBaggageInformationPolicy {

        private final NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory;

        private MyCompanyOneWayAfterTheFirstOfNovember(NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory) {
            this.newMyCompanyHandBaggageInformationFactory = newMyCompanyHandBaggageInformationFactory;
        }

        @Override
        public boolean canHandle(Flight flight) {
            return flight.isOneWay()
                    && flight.isMyCompany()
                    && flight.getOutboundDepartureDate().isAfter(FIRST_OF_NOVEMBER);
        }

        @Override
        public HandBaggageInformation getFrom(String renderLanguage) {
            return this.newMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }
    }

-    private class MyCompanyOneWayBeforeTheFirstOfNovember {
+    private class MyCompanyOneWayBeforeTheFirstOfNovember implements HandBaggageInformationPolicy {
        private final OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory;

        private MyCompanyOneWayBeforeTheFirstOfNovember(OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory) {
            this.oldMyCompanyHandBaggageInformationFactory = oldMyCompanyHandBaggageInformationFactory;
        }

+       @Override
        public boolean canHandle(Flight flight) {
            return flight.isOneWay()
                    && flight.isMyCompany()
                    && !flight.getOutboundDepartureDate().isAfter(FIRST_OF_NOVEMBER);
        }

+       @Override
        public HandBaggageInformation getFrom(String renderLanguage) {
            return this.oldMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }
    }

-    private class MyCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember {
+    private class MyCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember implements HandBaggageInformationPolicy {
        private final NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory;

        public MyCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember(NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory) {
            this.newMyCompanyHandBaggageInformationFactory = newMyCompanyHandBaggageInformationFactory;
        }

+       @Override
        public boolean canHandle(Flight flight) {
            return !flight.isOneWay()
                    && flight.isMyCompany()
                    && (flight.getOutboundDepartureDate().isAfter(FIRST_OF_NOVEMBER)
                        || flight.getReturnDepartureDate().isAfter(FIRST_OF_NOVEMBER)
                    );
        }

+       @Override
        public HandBaggageInformation getFrom(String renderLanguage) {
            return this.newMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }
    }

-    private class MyCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember {
+    private class MyCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember implements HandBaggageInformationPolicy {
        private final OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory;

        private MyCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember(OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory) {
            this.oldMyCompanyHandBaggageInformationFactory = oldMyCompanyHandBaggageInformationFactory;
        }

+        @Override
        public boolean canHandle(Flight flight) {
            return !flight.isOneWay()
                    && flight.isMyCompany()
                    && (!(flight.getOutboundDepartureDate().isAfter(FIRST_OF_NOVEMBER)
                        || flight.getReturnDepartureDate().isAfter(FIRST_OF_NOVEMBER))
                    );
        }

+        @Override
        public HandBaggageInformation getFrom(String renderLanguage) {
            return oldMyCompanyHandBaggageInformationFactory.from(renderLanguage);
        }
    }
}

Then it’s time to extract each policy into its own file and move all the policies into a package, that, for the sake of giving meaningful names, we call policy. To do this, we need to duplicate our threshold constant into more than one policy implementation. This could be arguable as it is a duplication. Of course, there are alternatives to this, like making the constant public, but then we have the problem to decide where to put it.
So, given the nature of our real case problem, in which the date will pass soon, and its very urgent time to market, we decide to apply the ostrich algorithm (i.e. we are ignore this discussion) and prefer duplication over other solutions 🙈 🙉 🙊.

Also we move the creation of all the policies before the evaluation, for a reason you will understand in the next step. (Source code)

public class HandBaggageInformationFactory {
    public HandBaggageInformation from(Order order, TranslationRepository translationRepository, String renderLanguage, Integer flightId) {
        Flight flight = order.findFlight(flightId);

        NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory =
                new NewMyCompanyHandBaggageInformationFactory(translationRepository);
        OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory =
                new OldMyCompanyHandBaggageInformationFactory(translationRepository);
        NotMyCompanyHandBaggageInformationFactory notMyCompanyHandBaggageInformationFactory =
                new NotMyCompanyHandBaggageInformationFactory();

        MyCompanyOneWayAfterTheFirstOfNovember myCompanyOneWayAfterTheFirstOfNovember =
                new MyCompanyOneWayAfterTheFirstOfNovember(newMyCompanyHandBaggageInformationFactory);
+       MyCompanyOneWayBeforeTheFirstOfNovember myCompanyOneWayBeforeTheFirstOfNovember =
+               new MyCompanyOneWayBeforeTheFirstOfNovember(oldMyCompanyHandBaggageInformationFactory);
+       MyCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember myCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember =
+               new MyCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember(newMyCompanyHandBaggageInformationFactory);
+       MyCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember myCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember = new
+               MyCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember(oldMyCompanyHandBaggageInformationFactory);

        if (myCompanyOneWayAfterTheFirstOfNovember.canHandle(flight)) {
            return myCompanyOneWayAfterTheFirstOfNovember.getFrom(renderLanguage);
        }

-       MyCompanyOneWayBeforeTheFirstOfNovember myCompanyOneWayBeforeTheFirstOfNovember =
-               new MyCompanyOneWayBeforeTheFirstOfNovember(oldMyCompanyHandBaggageInformationFactory);
        if (myCompanyOneWayBeforeTheFirstOfNovember.canHandle(flight)) {
            return myCompanyOneWayBeforeTheFirstOfNovember.getFrom(renderLanguage);
        }

-       MyCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember myCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember =
-               new MyCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember(newMyCompanyHandBaggageInformationFactory);
        if (myCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember.canHandle(flight)) {
            return myCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember
                    .getFrom(renderLanguage);
        }

-       MyCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember myCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember = new
-               MyCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember(oldMyCompanyHandBaggageInformationFactory);
        if (myCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember.canHandle(flight)) {
            return myCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember.getFrom(renderLanguage);
        }

        return notMyCompanyHandBaggageInformationFactory.make();
    }
}

5. Inject the chain at construction time

Now that we have all the policies created at the same point of our code, we can put all of them inside a list, loop over the list and just apply the first policy satisfying the condition. In this way, we remove the chain of if in favour of a chain of responsibility. (Source code)

public class HandBaggageInformationFactory {
    public HandBaggageInformation from(Order order, TranslationRepository translationRepository, String renderLanguage, Integer flightId) {
        Flight flight = order.findFlight(flightId);

        NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory =
                new NewMyCompanyHandBaggageInformationFactory(translationRepository);
        OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory =
                new OldMyCompanyHandBaggageInformationFactory(translationRepository);
        NotMyCompanyHandBaggageInformationFactory notMyCompanyHandBaggageInformationFactory =
                new NotMyCompanyHandBaggageInformationFactory();

        HandBaggageInformationPolicy myCompanyOneWayAfterTheFirstOfNovember =
                new MyCompanyOneWayAfterTheFirstOfNovember(newMyCompanyHandBaggageInformationFactory);
        HandBaggageInformationPolicy myCompanyOneWayBeforeTheFirstOfNovember =
                new MyCompanyOneWayBeforeTheFirstOfNovember(oldMyCompanyHandBaggageInformationFactory);
        HandBaggageInformationPolicy myCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember =
                new MyCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember(newMyCompanyHandBaggageInformationFactory);
        HandBaggageInformationPolicy myCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember = new
                MyCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember(oldMyCompanyHandBaggageInformationFactory);

+       List<HandBaggageInformationPolicy> policies = Arrays.asList(
+               myCompanyOneWayAfterTheFirstOfNovember,
+               myCompanyOneWayBeforeTheFirstOfNovember,
+               myCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember,
+               myCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember
+       );
+
+       for (HandBaggageInformationPolicy policy : policies) {
+           if (policy.canHandle(flight)) {
+               return policy.getFrom(renderLanguage);
+           }
+       }

-       if (myCompanyOneWayAfterTheFirstOfNovember.canHandle(flight)) {
-           return myCompanyOneWayAfterTheFirstOfNovember.getFrom(renderLanguage);
-       }
-       
-       if (myCompanyOneWayBeforeTheFirstOfNovember.canHandle(flight)) {
-           return myCompanyOneWayBeforeTheFirstOfNovember.getFrom(renderLanguage);
-       }
-       
-       if (myCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember.canHandle(flight)) {
-           return myCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember
-                           .getFrom(renderLanguage);
-       }
-       if (myCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember.canHandle(flight)) {
-           return myCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember.getFrom(renderLanguage);
-       }

        return notMyCompanyHandBaggageInformationFactory.make();
    }
}

If you prefer, you can use a stream instead of a classical loop.

public class HandBaggageInformationFactory {
    public HandBaggageInformation from(Order order, TranslationRepository translationRepository, String renderLanguage, Integer flightId) {
        Flight flight = order.findFlight(flightId);

        NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory =
                new NewMyCompanyHandBaggageInformationFactory(translationRepository);
        OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory =
                new OldMyCompanyHandBaggageInformationFactory(translationRepository);
        NotMyCompanyHandBaggageInformationFactory notMyCompanyHandBaggageInformationFactory =
                new NotMyCompanyHandBaggageInformationFactory();

        HandBaggageInformationPolicy myCompanyOneWayAfterTheFirstOfNovember =
                new MyCompanyOneWayAfterTheFirstOfNovember(newMyCompanyHandBaggageInformationFactory);
        HandBaggageInformationPolicy myCompanyOneWayBeforeTheFirstOfNovember =
                new MyCompanyOneWayBeforeTheFirstOfNovember(oldMyCompanyHandBaggageInformationFactory);
        HandBaggageInformationPolicy myCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember =
                new MyCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember(newMyCompanyHandBaggageInformationFactory);
        HandBaggageInformationPolicy myCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember = new
                MyCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember(oldMyCompanyHandBaggageInformationFactory);

        List<HandBaggageInformationPolicy> policies = Arrays.asList(
                myCompanyOneWayAfterTheFirstOfNovember,
                myCompanyOneWayBeforeTheFirstOfNovember,
                myCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember,
                myCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember
        );
        
+       return policies.stream()
+               .filter(policy -> policy.canHandle(flight))
+               .findFirst()
+               .map(policy -> policy.getFrom(renderLanguage))
+               .orElse(notMyCompanyHandBaggageInformationFactory.make());

-       for (HandBaggageInformationPolicy policy : policies) {
-           if (policy.canHandle(flight)) {
-               return policy.getFrom(renderLanguage);
-           }
-       }
-        
-       return notMyCompanyHandBaggageInformationFactory.make();
    }
}

Now we are going to move the responsibility of creating the policies in a new class, named HandBaggagePoliciesFactory. The purpose here, is to have each class which performs a single operation. Does it sound familiar 🤔? No? Yes? Well, this is the Single Responsibility Principle 🤩.
Again, if you use Idea, you can use its Extract method object feature. I know, it’s getting kind of repetitive 😅 but this is one of the refactoring commands we use most frequently when we refactor code, so using it makes you save a lot of time. (Source code)

public class HandBaggageInformationFactory {
    public HandBaggageInformation from(Order order, TranslationRepository translationRepository, String renderLanguage, Integer flightId) {
        Flight flight = order.findFlight(flightId);

-       NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory =
-               new NewMyCompanyHandBaggageInformationFactory(translationRepository);
-       OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory =
-               new OldMyCompanyHandBaggageInformationFactory(translationRepository);
        NotMyCompanyHandBaggageInformationFactory notMyCompanyHandBaggageInformationFactory =
               new NotMyCompanyHandBaggageInformationFactory();

-       HandBaggageInformationPolicy myCompanyOneWayAfterTheFirstOfNovember =
-               new MyCompanyOneWayAfterTheFirstOfNovember(newMyCompanyHandBaggageInformationFactory);
-       HandBaggageInformationPolicy myCompanyOneWayBeforeTheFirstOfNovember =
-               new MyCompanyOneWayBeforeTheFirstOfNovember(oldMyCompanyHandBaggageInformationFactory);
-       HandBaggageInformationPolicy myCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember =
-               new MyCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember(newMyCompanyHandBaggageInformationFactory);
-       HandBaggageInformationPolicy myCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember = new
-               MyCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember(oldMyCompanyHandBaggageInformationFactory);
-
-       List<HandBaggageInformationPolicy> policies = Arrays.asList(
-               myCompanyOneWayAfterTheFirstOfNovember,
-               myCompanyOneWayBeforeTheFirstOfNovember,
-               myCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember,
-               myCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember
-       );

+       List<HandBaggageInformationPolicy> policies = new HandBaggagePoliciesFactory().make(translationRepository);

        return policies.stream()
                .filter(policy -> policy.canHandle(flight))
                .findFirst()
                .map(policy -> policy.getFrom(renderLanguage))
                .orElse(notMyCompanyHandBaggageInformationFactory.make());
    }

+   private static class HandBaggagePoliciesFactory {
+       public List<HandBaggageInformationPolicy> make(TranslationRepository translationRepository) {
+           NewMyCompanyHandBaggageInformationFactory newMyCompanyHandBaggageInformationFactory =
+                   new NewMyCompanyHandBaggageInformationFactory(translationRepository);
+           OldMyCompanyHandBaggageInformationFactory oldMyCompanyHandBaggageInformationFactory =
+                   new OldMyCompanyHandBaggageInformationFactory(translationRepository);
+
+           HandBaggageInformationPolicy myCompanyOneWayAfterTheFirstOfNovember =
+                   new MyCompanyOneWayAfterTheFirstOfNovember(newMyCompanyHandBaggageInformationFactory);
+           HandBaggageInformationPolicy myCompanyOneWayBeforeTheFirstOfNovember =
+                   new MyCompanyOneWayBeforeTheFirstOfNovember(oldMyCompanyHandBaggageInformationFactory);
+           HandBaggageInformationPolicy myCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember =
+                   new MyCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember(newMyCompanyHandBaggageInformationFactory);
+           HandBaggageInformationPolicy myCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember = new
+                   MyCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember(oldMyCompanyHandBaggageInformationFactory);
+
+           return Arrays.asList(
+                   myCompanyOneWayAfterTheFirstOfNovember,
+                   myCompanyOneWayBeforeTheFirstOfNovember,
+                   myCompanyRoundTripAtLeastOneDepartureAfterTheFirstOfNovember,
+                   myCompanyRoundTripAllDeparturesBeforeTheFirstOfNovember
+           );
+       }
+   }
}

And after extracting the class HandBaggagePoliciesFactory in its own file, we are going to inject the policies as parameter at construction time of HandBaggageInformationFactory. In case of Idea, you can make the IDE work for you. You can just make the policies variable of from method become a field, with the command Extract field, decide to define it in the constructor and, then, simply use the Extract parameter feature in order to update all the constructors of your object.

public class HandBaggageInformationFactory {

+   private final List<HandBaggageInformationPolicy> handBaggageInformationPolicies;

+   public HandBaggageInformationFactory(List<HandBaggageInformationPolicy> handBaggageInformationPolicies) {
+       this.handBaggageInformationPolicies = handBaggageInformationPolicies;
+   }

    public HandBaggageInformation from(Order order, String renderLanguage, Integer flightId) {
        Flight flight = order.findFlight(flightId);

        NotMyCompanyHandBaggageInformationFactory notMyCompanyHandBaggageInformationFactory =
                new NotMyCompanyHandBaggageInformationFactory();
                
-       List<HandBaggageInformationPolicy> policies = new HandBaggagePoliciesFactory().make(translationRepository);                

        return handBaggageInformationPolicies.stream()
                .filter(policy -> policy.canHandle(flight))
                .findFirst()
                .map(policy -> policy.getFrom(renderLanguage))
                .orElse(notMyCompanyHandBaggageInformationFactory.make());
    }

}

In our example, only the HandBaggageInformationFactoryTest has been updated. Notice, though, that we don’t need to perform changes on the tests at all. They still pass. (Source code)

public class HandBaggageInformationFactoryTest {

    private HandBaggageInformationFactory handBaggageInformationFactory;

    @Before
    public void setUp() {
        TranslationRepository translationRepository = Mockito.mock(TranslationRepository.class);
-        handBaggageInformationFactory = new HandBaggageInformationFactory();
+        handBaggageInformationFactory =
+                new HandBaggageInformationFactory(HandBaggagePoliciesFactory.make(translationRepository));
+        }
        
    //The rest is the same    
}

In order to maintain the same level of abstraction, we are going to inject also the NotMyCompanyHandBaggageInformationFactory into the HandBaggageInformationFactory.

public class HandBaggageInformationFactory {

    private final List<HandBaggageInformationPolicy> handBaggageInformationPolicies;
+   private final NotMyCompanyHandBaggageInformationFactory fallbackHandBaggageFactory;

-   public HandBaggageInformationFactory(List<HandBaggageInformationPolicy> handBaggageInformationPolicies) {
+   public HandBaggageInformationFactory(List<HandBaggageInformationPolicy> handBaggageInformationPolicies,
+                                        NotMyCompanyHandBaggageInformationFactory fallbackHandBaggageFactory) {
        this.handBaggageInformationPolicies = handBaggageInformationPolicies;
+       this.fallbackHandBaggageFactory = fallbackHandBaggageFactory;
+   }

    public HandBaggageInformation from(Order order, String renderLanguage, Integer flightId) {
        Flight flight = order.findFlight(flightId);
        
-       NotMyCompanyHandBaggageInformationFactory notMyCompanyHandBaggageInformationFactory =
-                       new NotMyCompanyHandBaggageInformationFactory();

        return handBaggageInformationPolicies.stream()
                .filter(policy -> policy.canHandle(flight))
                .findFirst()
                .map(policy -> policy.getFrom(renderLanguage))
                .orElse(fallbackHandBaggageFactory.make());
    }
}

And, then, again, the HandBaggageInformationFactoryTest gets updated. (Source code)

public class HandBaggageInformationFactoryTest {

 private HandBaggageInformationFactory handBaggageInformationFactory;

     @Before
     public void setUp() {
         TranslationRepository translationRepository = Mockito.mock(TranslationRepository.class);
         handBaggageInformationFactory =
            new HandBaggageInformationFactory(
                HandBaggagePoliciesFactory.make(translationRepository),
+                    new NotMyCompanyHandBaggageInformationFactory()
            );
         
     //The rest is the same    
    }
}

And, that’s it. You could keep on working on this piece of code to improve it more and more. But for post, we reached our goal, so no more refactoring 👏.

Conclusion

In this article we saw how to transform, step by step, a nested if structure into a chain of responsibility. The purpose of it is to make the code more readable and though easier to extend without introducing bugs. The main steps are flatten the if structure into a sequence of plain if clauses, extract each if clause and its correspondent effect into a separate class, extract an interface which is common to all the extracted classes, create an array containing all the classes, loop over it by using the first item of the list that is able to handle the case you are dealing with, inject the list of classes in order to make the class work with any combination of rules that you want.
Additionally, we saw some features of Idea IDE that allow us to perform most of the refactoring operations automatically, with only a few shortcuts.