DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Spring Strategy Pattern Example
  • Simplify Java: Reducing Unnecessary Layers and Interfaces [Video]
  • Using Python Libraries in Java
  • Designing a Java Connector for Software Integrations

Trending

  • Comprehensive Guide to Property-Based Testing in Go: Principles and Implementation
  • Unlocking Data with Language: Real-World Applications of Text-to-SQL Interfaces
  • Blue Skies Ahead: An AI Case Study on LLM Use for a Graph Theory Related Application
  • Cookies Revisited: A Networking Solution for Third-Party Cookies
  1. DZone
  2. Coding
  3. Java
  4. Modern Strategy Pattern in Functional Java

Modern Strategy Pattern in Functional Java

This article shows how to use a strategy pattern with a pinch of enums and functional syntactic sugar in functional Java.

By 
Jakub JRZ user avatar
Jakub JRZ
·
Updated Aug. 17, 22 · Tutorial
Likes (6)
Comment
Save
Tweet
Share
4.6K Views

Join the DZone community and get the full member experience.

Join For Free

There's a way to think about design patterns that stuck with me. Like Venkat said at the beginning of his 2019 Devoxx talk, they are a lot like grandma's recipes. We all love when our grandmas cook for us. But try to ask for the recipes —the amount of flour or sugar to use is never precise. And when you prepare the food yourself, it comes out completely different.

In our biggest Java project at Evojam right now, we have tweaked the recipe for strategy pattern. We added our personal touch with a pinch of enums and functional syntactic sugar. As always, the easiest way to explain it is with an example. Let's jump right into it.

Setting the Scene

When you take a picture, exposure depends on three values. If you pick them at random, chances are the result will come out more bright or blurry than you expected. Luckily, even vintage analogue cameras let you use modes other than full manual. The following class represents all controls you need before taking a picture:

Java
 
@lombok.Value
class CameraControls {

    Mode mode;

    FilmSpeed filmSpeed;
    Aperture aperture;
    Shutter shutter;
}


In analogue photography, the speed (also called ISO) depends on the film roll that you use. Your camera then either reads it or needs you to set it. You do this once after inserting a new roll.

This leaves you with aperture and shutter. We go about them differently in each mode:

Java
 
enum Mode {
    MANUAL, APERTURE_PRIORITY
}


When in manual mode, both an aperture and shutter speed are what the user requested. If the picture comes out too dark or too bright, it is not the camera's fault. Aperture priority is different. As the name suggests, it uses the aperture set by the user to pick the right shutter speed considering a specific film speed. It is a balancing act, best illustrated by what is often called the ‘exposure triangle.’

exposure triangle

Cameras use light meters to pick the right shutter speed in aperture priority mode. Desirable areas of the triangle on a sunny day outside will be different than in your living room. This is why you need to adjust controls for the right exposure for each new scene.

All right, if you don't know about photography, it can all sound a bit overwhelming. It is not that complicated, though. Each of the controls has a finite set of values:

Java
 
enum FilmSpeed {
    _100, _200, _400, _800
}

enum Aperture {
    F2, F4, F8, F16
}

enum Shutter {
    _60, _250, _500, _1000
}


By the time you press the shutter release, film speed is already fixed for the roll. This leaves us with just two values to pass to a shoot() method:

Java
 
void shoot(Aperture aperture, Shutter shutter) {...}


Keeping It Simple

As I said before, modes influence the handling of aperture and shutter values. In the manual, the camera takes values from user controls for granted. This is exactly what happens inside the first if block below.

Aperture priority means you want to use the aperture value passed in controls. For shutter speed, you refer to the light meter. You provide it with film speed and desired aperture. In return, you get a shutter speed recommendation based on light measurement. You can follow this logic in the second if block.

Java
 
void shutterRelease(CameraControls controls, LightMeter meter) {

    if (controls.getMode() == Mode.MANUAL) {

        shoot(controls.getAperture(), controls.getShutter());

    } else if (controls.getMode() == Mode.APERTURE_PRIORITY) {

        Shutter shutter = meter.pickShutter(
                controls.getFilmSpeed(),
                controls.getAperture()
        );

        shoot(controls.getAperture(), shutter);
    }
}


One Step Too Far

As the saying goes, the only thing that's constant in software is change. No if statement inside a Java method is likely to remain untouched for long. Let alone two if statements inside one method.

In our case, one possible change request could be to handle a new mode. Shutter priority is the reverse of aperture priority. Let’s add it to the original enum:

Java
 
enum Mode {
    MANUAL, APERTURE_PRIORITY, SHUTTER_PRIORITY
}


Only adding the third value is very naive, but the code compiles. In our original design, no new modes were coming. When the photographer goes for shutter priority and presses shutter release, nothing happens. To handle the new mode, you need to add another if block. 

There must be a better way to handle this, though. And there is — it goes by the name of the strategy pattern.

The first thing to do is define the interface for picking shutter and aperture values. You can use a generic type T to represent either Shutter or Aperture:

Java
 
interface Picker<T> {
    T pick(CameraControls settings, LightMeter meter);
}


Modes differ in the way an aperture and shutter get picked. It seems natural to parameterize modes with Picker strategies. Introducing private final fields to the enum makes them obligatory and immutable. That's exactly what you need for each existing as well as any new mode in the future:

Java
 
@lombok.Getter
@lombok.RequiredArgsConstructor
enum Mode {

    MANUAL(),
    APERTURE_PRIORITY(),
    SHUTTER_PRIORITY();

    private final Picker<Aperture> aperturePicker;
    private final Picker<Shutter> shutterPicker;
}


For the code above to compile, you also need to pass two arguments to each constructor. Picker is a functional interface as it only has one method. You can implement it with simple Java lambdas:

Java
 
@lombok.Getter
@lombok.RequiredArgsConstructor
enum Mode {

    MANUAL(
            (controls, meter) -> controls.getAperture(),
            (controls, meter) -> controls.getShutter()
    ), 

    ...
}


Final Touches

On second thought, we could end up repeating ourselves. Both manual and aperture priority take aperture from user controls. Both manual and shutter priority take shutter speed from user controls. Keeping the DRY principle in mind, let's extract these lambdas to static methods.

Java
 
interface Picker<T> {

    T pick(CameraControls settings, LightMeter meter);

    static Aperture apertureFixed(CameraControls controls, 
                                  LightMeter meter) {
        return controls.getAperture();
    }

    static Shutter pickShutter(CameraControls controls, 
                                    LightMeter meter) {
        return meter.pickShutter(
                controls.getFilmSpeed(), 
                controls.getAperture()
        );
    }

    ...
}


You have to admit, the end result is a beautiful piece of clean code:

Java
 
@lombok.Getter
@lombok.RequiredArgsConstructor
enum Mode {

    MANUAL(Picker::apertureFixed, Picker::shutterFixed),
    APERTURE_PRIORITY(Picker::apertureFixed, Picker::pickShutter),
    SHUTTER_PRIORITY(Picker::pickAperture, Picker::shutterFixed);

    private final Picker<Aperture> aperturePicker;
    private final Picker<Shutter> shutterPicker;
}


First, it is self-explanatory, thanks to the careful choice of method and field names. Second, it won't let your future self introduce a new mode without handling both pickers. Finally, it lends itself to elegant use in other parts of the code with no if statements at all:

Java
 
@lombok.Value
class CameraControls {

    Mode mode;

    Speed speed;
    Aperture aperture;
    Shutter shutter;

    Aperture pickAperture(LightMeter meter) {
        return mode.getAperturePicker().pick(this, meter);
    }

    Shutter pickShutter(LightMeter meter) {
        return mode.getShutterPicker().pick(this, meter);
    }
}

void shutterRelease(CameraControls controls, LightMeter meter) {

    Aperture aperture = controls.pickAperture(meter);
    Shutter shutter = controls.pickShutter(meter);

    shoot(aperture, shutter);
}


Make sure you compare this code to the earlier shutterRelease() with two if statements. Which one is easier to read? Remember, at that point, we were not even handling the third mode. For another mode, we would have needed yet another if block.

Keeping it SOLID

Strategy pattern makes our implementation SOLID in more than one way. The only reason to change the Mode enum is to handle a new choice. It is easy to extend the enum with new modes without modifying shutterRelease(). 

Go ahead and find proof of all five SOLID principles yourself. Please put them in the comments section below. Once you do that, you should start noticing perfect use cases in your current project.

Strategy pattern Java (programming language)

Published at DZone with permission of Jakub JRZ. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Spring Strategy Pattern Example
  • Simplify Java: Reducing Unnecessary Layers and Interfaces [Video]
  • Using Python Libraries in Java
  • Designing a Java Connector for Software Integrations

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!