CHICIO CODING

Dirty clean code. Creative Stuff. Stuff.

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.

Rich iOS notifications with Notification Content App Extension

In this post I will talk about iOS notification customization with Notification Content App Extension


During a workshop I recently attended I had the opportunity to explore a new interesting app extension type available in the iOS SDK: Notification Content App Extension. This extension has been added by Apple in iOS 10. By using this extension it is possible to customize the UI of your push and local notification. In this way you can show more engaging notification to your users :grin:.
Let’s see an example where we create a notification with custom UI using this extension type. To do that I will use some assets taken from the nasa photo journal. You can find the complete example in this github repository. Let’s start from the beginning: add a Notification Content Extension to your app. To do that just select the + button in the target section that appears after selecting the xcodeproj file.

add target

The extension that is created contains a NotificationViewController that adheres to the UNNotificationContentExtension. The documentation for this protocol says:

An object that presents a custom interface for a delivered local or remote notification. The UNNotificationContentExtension protocol provides the entry point for a notification content app extension, which displays a custom interface for your app’s notifications. You adopt this protocol in the custom UIViewController subclass that you use to present your interface. You create this type of extension to improve the way your notifications are presented, possibly by adding custom colors and branding, or by incorporating media and other dynamic content into your notification interface.

So if a UIViewController inside a Notification Content Extension adhere to the UNNotificationContentExtension we are able to access to the notification content and we can customize its UI. In the extension there’s also a MainInterface.storyboard that contains a single controller associated with the
NotificationViewController previously mentioned. We can use this storyboard to customize the notification UI using interface builder. So let’s start by defining the interface in the storyboard. Below in the image there’s the final result.

notification storyboard

Then we can fill the UI with the notification content we receive in the func didReceive(_ notification: UNNotification) of the NotificationViewController controller that implements the protocol shown above. Below you can find its source code.

import UserNotifications
import UserNotificationsUI

class NotificationViewController: UIViewController, UNNotificationContentExtension {
    @IBOutlet weak var nasaImage: UIImageView!
    @IBOutlet weak var nasaTitle: UILabel!
    @IBOutlet weak var nasaDescription: UILabel!
    
    func didReceive(_ notification: UNNotification) {
        if let validTitle = notification.request.content.userInfo["title"] as? String,
            let validDescription = notification.request.content.userInfo["description"] as? String,
            let validImageName = notification.request.content.userInfo["imageName"] as? String {
            nasaTitle.text = validTitle
            nasaDescription.text = validDescription
            nasaImage.image = UIImage(named: validImageName)
        }
    }
}

Now to test our controller and our new notification UI, we can create a local notification. We will use some data taken from the nasa photo journal. We will create a NasaLocalNotificationBuilder that contains all the notification creation logic. This is the final result.

import UserNotifications

class NasaLocalNotificationBuilder {
    private let notificationCenter: UNUserNotificationCenter = UNUserNotificationCenter.current()
    private var notificationActions: [UNNotificationAction] = []
    private var notificationContent = UNMutableNotificationContent()
    
    func setActions() -> NasaLocalNotificationBuilder {
        notificationActions.append(
            UNNotificationAction(identifier: "view",
                                 title: "View Photo in app",
                                 options: [.foreground, .authenticationRequired])
        )
        notificationActions.append(
            UNNotificationAction(identifier: "skip",
                                 title: "Skip",
                                 options: [])
        )
        return self
    }
    
    func setCategory() -> NasaLocalNotificationBuilder {
        let notificationCategory = UNNotificationCategory(identifier: "NasaDailyPhoto",
                                                          actions: notificationActions,
                                                          intentIdentifiers: [],
                                                          hiddenPreviewsBodyPlaceholder: "",
                                                          options: .customDismissAction)
        notificationCenter.setNotificationCategories([notificationCategory])
        return self
    }
    
    func setContent() -> NasaLocalNotificationBuilder {
        notificationContent.title = "Your Nasa Daily Photo"
        notificationContent.body = "Long press to see you daily nasa photo"
        notificationContent.sound = UNNotificationSound.default()
        notificationContent.categoryIdentifier = "NasaDailyPhoto"
        notificationContent.userInfo = [
            "title" : "At the Heart of Orion",
            "description" : """
            Near the center of this sharp cosmic portrait, at the heart of the Orion Nebula, are four hot, massive
            stars known as the Trapezium. Gathered within a region about 1.5 light-years in radius, they dominate the
            core of the dense Orion Nebula Star Cluster. Ultraviolet ionizing radiation from the Trapezium stars,
            mostly from the brightest star Theta-1 Orionis C powers the complex star forming region's entire visible
            glow. About three million years old, the Orion Nebula Cluster was even more compact in its younger years
            and a recent dynamical study indicates that runaway stellar collisions at an earlier age may have formed
            a black hole with more than 100 times the mass of the Sun. The presence of a black hole within the cluster
            could explain the observed high velocities of the Trapezium stars. The Orion Nebula's distance of some
            1,500 light-years would make it the closest known black hole to planet Earth.
            """,
            "imageName" : "orion"
        ]
        return self
    }
    
    func build() {
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
        let request = UNNotificationRequest(identifier: "NasaDailyPhoto",
                                            content: notificationContent,
                                            trigger: trigger)
        notificationCenter.add(request, withCompletionHandler: nil)
    }
}

Then we can call the notification builder in the main controller of the app ViewController to generate the notification.

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        NasaLocalNotificationBuilder()
            .setActions()
            .setCategory()
            .setContent()
            .build()
    }
}

Now we have to go back in our notification content extension and set some attribute in its info.plist. In particular we have to set:

  • UNNotificationExtensionCategory to the notification category that we want to customize. In our case we use NasaDailyPhoto, the value we previously set in the builder
  • UNNotificationExtensionDefaultContentHidden to hide the standard notification content when the user long press the notification.
  • if needed it is possible also to set the UNNotificationExtensionInitialContentSizeRatio to a value different than 1 to customize the aspect ration of our notification.

We’re ready to test our implementation. Below you can find a video with the final result of our implementation.

That’s all for Notification Content App Extension on iOS. Let’s start to test them in your app :relaxed:.

Blender tutorial: advanced modeling

In this new post of the series Blender tutorial I will talk about advanced modeling in Blender.


In the previous post of the series “Blender tutorial” we talked about the basics of modeling in Blender. In this post we will study Blender advanced modeling tools.
Let’s start from modifiers. All the operation we saw in the previous modeling post were all “one time operation”: after we apply them we can’t change their properties to modify the result we obtain. Modifiers let us apply modification to a mesh that is possible to revert, modify and preview. When we are sure about the result, we can apply the modifier to get the mesh modification we want.
We can apply a modifier by selecting an object (right click on it) and go under the properties panel and click on the wrench icon. Here we can select a modifier from the list Add modifier.

blender add modifier

For example let’s try to add a simple deform to a cube. After adding it we see our cube deformed. We can than tweak the parameters of the deformation as we prefer. Each modifier as its own properties, based of which type of modification it applies to the mesh. Anyway there are some common button to:

  • show/hide the modifier result during rendering
  • show/hide the modifier result in the 3D window
  • show/hide the modifier result in the 3D window in Edit mode
  • show/hide the cage of the modifier in the 3D window

blender add modifier icons

After that we have the subdivision surface. As stated on Wikipedia, subdivision surface is a technique discovered simultaneously by Edwin Catmull and Jim Clark:

A subdivision surface, in the field of 3D computer graphics, is a method of representing a smooth surface via the specification of a coarser piecewise linear polygon mesh. The smooth surface can be calculated from the coarse mesh as the limit of recursive subdivision of each polygonal face into smaller faces that better approximate the smooth surface.

This basically means that with subdivision surface we can approximate smoother surfaces starting from another one.
In Blender we can apply subdivision surface as a modifier.

blender add subdivision surface

After the subdivision is applied it is possible to modify the mesh only on the original edges and faces of the mesh. In fact this elements will be the only one that we can select from our mesh.

blender added subdivision surface

Another interesting modifier is the mirror modifier. It is especially useful when you need to do Symmetric modeling. After applying this modifier every operation you made on one side of your mesh will be mirrored on the other side. For this reason, especially when you’re modeling some character/organic mesh, it is useful to delete one side of the mesh and mirror the other one (with all the modification you do). In the option of the modifier you can choose on which axis the mirroring must be applied.

blender mirror modifier

The last interesting modifier (for now) we will see it is the boolean modifier. This modifier let us modify meshes by calculate difference, union and intersection between them.

blender boolean modifier blender boolean modifier operation option

Another interesting tool is the “Loop Cut and Slide”. With this option we can add edge loops to our mesh. This tool is useful especially when you need to have more mesh polygon to model the mesh details you want.

blender loop edge and slide

Then we have the joining mesh tool and the stitching vertices tool. For the first one, as the name suggest, you can combine multiple mesh into a single one in blender. To join two or more meshes, we have to select them while we are object mode. Remember: the last one selected will be the mesh to which the other ones will be joined. After that we can apply the join mesh using:

  • the menu option Object -> Join
  • the shortcut “ctrl + j keys”

blender join meshes

After joining two or more meshes, we will need to fill the gap between then. We can do that by using the merge tool, the remove double tool and the snap element tool to stitch the vertices that are on the border of the meshes. The vertices must be open: they need to be without a connection on one side. So after selecting one vertex we can snap it using the snap element tool. We can activate it using the button in the bottom bar of the 3D window. After activating it we can grab the selected vertex by pressing the g key, and snap it to our target vertex.

blender snap element

After snapping vertices we have to remove duplicates. We can do it using the merge tool or using the remove doubles tool. We can find these tools:

  • *under Mesh -> *
  • from the contextual menu that you can show by using the shortcut ctrl + v key for vertices (NB.: there are also contextual menus for faces and edges that we can show using ctrl + f key and ctrl + e key)

If we decide to go with the remove doubles tools, after applying the operation a popup at the top of the 3D window will appear that will tell us the number of duplicated vertices removed.

blender merge remove tools

In blender it is possible to model also text. We can add text by selecting Add -> Text. We can modify the text by selecting the edit mode: a text cursor will appear so that we can modify the text. In the property panel a special tab will let as customize the text property.

blender add text blender text properties

Last but not least is the vertex groups tool. This tool allow us to save selection of vertices so that we are able to select them later. We can create a vertex group in the property panel in the corresponding tab. In this tab we have buttons to:

  • add/remove vertices to a group
  • select/deselect the vertices of a group

blender vertex group

In the next post we will discuss about outliner, layers, groups, hierarchies and scenes.