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

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • While Performing Dependency Selection, I Avoid the Loss Of Sleep From Node.js Libraries' Dangers
  • Why I Started Using Dependency Injection in Python
  • Understanding ldd: The Linux Dynamic Dependency Explorer
  • Understanding the Two Schools of Unit Testing

Trending

  • Data Lake vs. Warehouse vs. Lakehouse vs. Mart: Choosing the Right Architecture for Your Business
  • Next Evolution in Integration: Architecting With Intent Using Model Context Protocol
  • How Kubernetes Cluster Sizing Affects Performance and Cost Efficiency in Cloud Deployments
  • Endpoint Security Controls: Designing a Secure Endpoint Architecture, Part 2
  1. DZone
  2. Software Design and Architecture
  3. Security
  4. Compile- and Run-Time Dependency

Compile- and Run-Time Dependency

This lesson in refactoring explores using the Strategy Pattern to break a compile-time dependency into a run-time dependency for more flexible code.

By 
Murat Gungor user avatar
Murat Gungor
DZone Core CORE ·
Updated Apr. 03, 18 · Tutorial
Likes (7)
Comment
Save
Tweet
Share
23.7K Views

Join the DZone community and get the full member experience.

Join For Free

Here is the scenario we have:

You can think of all the code below as being created by you, meaning you are the owner and you already locked the files in the Configuration Management System so that others cannot alter it without you knowing. But you have grown tired of random change requests asking to use different sorting algorithms for the sort function.

abstract class Account {
    // ...
    // Many lines for generic bank account needs
    // ...

    // Default sorting method for transactions etc.
    public void sort() {
        System.out.println("Insertion Sort");
    }
}

class Saving extends Account {
    // ...
    // Many lines for saving account needs
    // ...

    // Saving accounts likes to use Bubble sort for sorting needs.
    @Override
    public void sort() {
        System.out.println("Bubble Sort");
    }
}
class Debit extends Account {
    // ...
    // Many lines for debit account needs
    // ...

    //Inherits the base class sorting functions
}

class Dividend extends Account {
    // ...
    // Many lines for dividend account needs
    // ...
    @Override
    public void sort() {
        System.out.println("Bubble Sort");
    }
}

// For demonstration purposes, we have only the main method here
public class CompileRunTimeDependency {

    public static void main(String[] args) {
        Account accSaving = new Saving();
        accSaving.sort(); //Prints: Bubble Sort
        Account accDebit = new Debit();
        accDebit.sort(); //Prints: Insertion Sort
        Account accDividend = new Dividend();
        accDividend.sort(); //Prints: Bubble Sort
        /* CONSOLE OUTPUT:
         * Bubble Sort
         * Insertion Sort
         * Bubble Sort
         * 
         * */
    }
}


Current Picture

If a client creates a Saving account instance, we know it will always use Bubble Sort. It is already set in stone. Or, if a client creates a Debit object, it will always call an Insertion sort. It is not possible to change sorting behavior without changing code — this is a compile-time dependency.

Change Request Pops

But a change request has just arrived wanting to use the Insertion sort algorithm for Dividend. This is possible, but we need to change the code — again, a compile-time dependency. As said before, you don't want anyone to change your existing code, including yourself!

Let's Critique the Current Design

We've violated the Single Responsibility Principle. Sorting is not the job of a bank account — a bank account should do only bank account things.

Second: We have code duplication. We copy/pasted the Bubble sort algorithm in Saving and Dividend accounts.

How to Accommodate the Change Request

We have a compile-time dependency due to inheritance. Therefore, we have to modify the existing code to accommodate the change request above. But you are tired of modifying the code for a never-ending request for using a different sorting algorithm. And in the future, there would be a request for even additional sorting algorithms, such as Selection, Merge Sort, etc. Life will be harder.

Refactoring Time

First step: Take out each sorting algorithm to its own class. But wait a minute. Remember the design principle Program to Interface. This way, we can break the compile-time dependency and reach a run-time dependency. This will enable our clients to change sorting algorithms as they want, without asking you to change your code.

interface SortingServices {
    void sort();
}

class BubbleSort implements SortingServices {
    public void sort() {
        System.out.println("Bubble Sort");
    }
}
class InsertionSort implements SortingServices {
    public void sort() {
        System.out.println("Insertion Sort");
    }
}


We have just pulled out the sorting algorithms, added an interface to make our code have standard method names, and taken advantage of programming to interface to use the benefit of a run-time dependency.

Now we need to update Bank Account's code to use the moved out sorting algorithms. For this, we create an instance of the SortingServices interface and initialize it in a sort() function with a default function of InsertionSort.

abstract class Account {
    //We added reference to interface
    SortingServices sortService;
    // ...
    // Many lines for generic bank account needs
    // ...

    // Default sorting method for transactions etc.
    public void sort() {
        // Default sorting still Insertion
        sortService = new InsertionSort();
        sortService.sort();
        //System.out.println("Insertion Sort");
    }
}


We need to change Saving and Dividend, too — so let's do it. In the constructor, we initialized sortService to BubbleSort since Saving uses BubbleSort. We have just added setSortingAlgorithm to enable our client to change the sorting algorithm at runtime. This is the advantage of the Program to Interface principle.

class Saving extends Account {
    // ...
    // Many lines for saving account needs
    // ...

    // Saving accounts like to use Bubble sort for sorting needs.

    public Saving() {
        //Default sorting for Saving Account
        sortService = new BubbleSort();
    }
    // We can change the sorting algorithm at run-time 
    // We do not need to change our code anymore for sorting algorithms
    public void setSortingAlgorithm(SortingServices sort) {
        this.sortService = sort;
    }

    @Override
    public void sort() {
        //Just call a sort method
        sortService.sort();
        //System.out.println("Bubble Sort");
    }
}


When we do the same update for Dividend, I noticed we have duplicated our code — the setSortingAlgorithm method. We can pull that method to the Account class to get rid of the code duplication.

class Dividend extends Account {
    // ...
    // Many lines for dividend account needs
    // ...

    public Dividend() {
        //Default sorting for Saving Account
        sortService = new BubbleSort();
    }
    // We can change the sorting algorithm at run-time 
    // We do not need to change our code anymore for sorting algorithm
    public void setSortingAlgorithm(SortingServices sort) {
        this.sortService = sort;
    }
    @Override
    public void sort() {
        //Just call a sort method
        sortService.sort();
        //System.out.println("Bubble Sort");
    }
}


Updated Account Class

We have just moved the setSortingAlgorithm method to the Account class and removed it from the derived classes (from the Dividend and Saving classes):

abstract class Account {
    //We added reference to interface
    SortingServices sortService = new InsertionSort();
    // ...
    // Many lines for generic bank account needs
    // ...

    // We can change sorting algorithm at run-time 
    // We do not need to change our code anymore for sorting algorithm
    public void setSortingAlgorithm(SortingServices sort) {
        this.sortService = sort;
    }

    // Default sorting method for transactions etc.
    public void sort() {
        // Default sorting still Insertion
        // sortService = new InsertionSort();
        sortService.sort();
        //System.out.println("Insertion Sort");
    }
}


Tested and it works as before. We just finished our refactoring.

Summary: What Have We Achieved?

We have just used the Strategy Pattern to break a compile-time dependency, and our code does not need to be changed anymore to use a different sorting algorithm. Our code can be enhanced independently; a new sorting algorithm can be added without disturbing your code. Our clients are happier; they do not need to beg you to change your code. By programming to interface, we break the compile-time dependency and create a run-time dependency.

Compile-time dependencies require code to be changed, but run-time dependencies do not.

Everyone is happy. Breaking dependency, reducing complexity, more flexible code.

Thanks for reading.

Dependency Sorting algorithm

Opinions expressed by DZone contributors are their own.

Related

  • While Performing Dependency Selection, I Avoid the Loss Of Sleep From Node.js Libraries' Dangers
  • Why I Started Using Dependency Injection in Python
  • Understanding ldd: The Linux Dynamic Dependency Explorer
  • Understanding the Two Schools of Unit Testing

Partner Resources

×

Comments

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
  • [email protected]

Let's be friends: