Becoming the Best Software Engineer: Pythonic Control Coupling

Spread the love

In this blog post, you will learn what control coupling is and why it’s important. Also, you will see a sample code and built-in python module that exhibits pythonic control coupling. Furthermore, you will learn methods to improve pythonic control coupling.

This blog post is part three of a series on pythonic coupling. You can read about pythonic stamp coupling in my previous blog post Becoming the Best Software Engineer: Pythonic Stamp Coupling.

What Is Control Coupling and Why Is It Important?

Two modules exhibit control coupling when one module can affect the control flow of another module by passing information into it (Ingeno, 2018). Typically, this is done by passing a mode or flag to a module.

It’s important to know what control coupling is because it is a moderately tight form of coupling. This means that it’s possible that maintenance and testing can become more difficult.

An Example of Pythonic Control Coupling

The following code sample of the lightbulb.py module has a constructor that takes a Boolean argument of whether the light bulb is on or off.

class LightBulb:
    def __init__(self, is_on : bool = True):
        self.__is_on = is_on
    
    def status(self):
        if self.__is_on:
            print("On.")
        else:
            print("Off.")
    
    def turn_on(self):
        self.__is_on = True
    
    def turn_off(self):
        self.__is_on = False

The portion of the above code that is control coupled is the status method because of the “if” statement. You can see that the “__is_on” instance variable controls the flow of the execution of code when reaching the conditional statement, which is information that is passed from outside the module, so any module that instantiates the “LightBulb” object will be control coupled to it.

Consider the following code for the main.py module:

from lightbulb import LightBulb

def main():
    # Instantiate a light bulb object that is in the off state.
    # Therefore, the main.py module is control coupled to LightBulb.
    light_bulb : LightBulb = LightBulb(is_on=False)

    # Get the status of the light bulb: on or off.
    light_bulb.status()

    # Turn the light bulb on.
    light_bulb.turn_on()

    # Get the status of the light bulb: on or off.
    light_bulb.status()

if __name__ == "__main__":
    main()

The main.py module is able to control the execution of code within the “LightBulb” module.

A Built-In Pythonic Module That Exhibits Control Coupling in the Standard Library

The logging module that is available in Python’s standard library exhibits control coupling. The following is a sample code that initializes a logger in a python module:

import logging

def main():
    # Setup Basic Configuration, and set logging level to INFO.
    logging.basicConfig(level=logging.INFO)

    # Get the logger given the name of this module.
    logger = logging.getLogger(__name__)

    # Print a logging message at the INFO level.
    logger.info("Logger Initialized")


if __name__ == "__main__":
    main()

By passing the “logging.INFO” flag to the “level” keyword argument of the “basicConfig” function of the logging module, we have controlled the flow of execution of code within the logger so that it prints at the INFO level and above, which means any logging associated with debugging will not be logged.

How Can We Improve Control Coupling?

Despite the fact that control coupling is permissible, it is a tighter coupling, so how can we make the code even better?

Abstraction and Polymorphism

One way to remove control coupling is to use abstraction and polymorphism. Let’s rewrite the “LightBulb” class to use polymorphism.

First, we will create a state.py module, which is the following:

from abc import ABC, abstractmethod

class Statable(ABC):
    @abstractmethod
    def state(self):
        pass

class OnState(Statable):
    def state(self):
        print("On.")

class OffState(Statable):
    def state(self):
        print("Off.")

The “abc” module enables you to create abstract classes in Python. The “Statable” abstract class is meant to represent states in an abstract way. The “OnState” and “OffState” classes implement the state method as on and off, respectively.

Next, the implementation of the “LightBulb” class becomes the following:

from state import Statable, OnState, OffState

class LightBulb:
    def __init__(self, state : Statable = OffState()):
        self.__state = state
    
    def status(self):
        self.__state.state()
    
    def turn_on(self):
        self.__state = OnState()

    def turn_off(self):
        self.__state = OffState()
    
    

Now, you can see that the implementation of the status method of the “LightBulb” module doesn’t have an “if” statement controlled by outside information, so there is no more control coupling. Instead, polymorphism is used to execute the correct code when getting the state of the “LightBulb” module.

The new implementation of the “LightBulb” module with the “Statable”, “OnState”, and “OffState” modules is known as the behavioral design pattern, which I will go into further detail in a future blog post. You may wonder what behavior the “LightBulb” exhibits. Its behavior is “being” in a state in this scenario.

Callback Functions

Another method to improve the control couped code is to use callback functions, which can be achieved with lambdas in python.

By using lambdas in Python, the implementation of the “LightBulb” module becomes the following:

class LightBulb:
    def __init__(self, state = lambda: print("Off.")):
        self.__state = state
    
    def status(self):
        self.__state()
    
    def turn_on(self):
        self.__state = lambda: print("On.")

    def turn_off(self):
        self.__state = lambda: print("Off.")

Now, the control coupling has been removed with lambdas. Instead of the “__state” instance variable being set to an outside Boolean piece of information that controls how the flow of execution of code happens within the “LightBulb” module, the “__state” instance variable is set to a lambda function that returns (or in this case, prints) the state of the “LightBulb” module.

In conclusion, you have learned that control coupling creates a pair of moderately coupled modules, which can possibly make testing and maintenance more difficult. Also, you have seen a sample code that demonstrates pythonic coupling, and you have learned that Python’s built-in logging module uses control coupling to set the logging level. Furthermore, you have seen how to enhance code that is coupled by controlling by utilizing polymorphism and callback functions.


If you found this blog post to be valuable, then you should consider doing the following:

Subscribe

* indicates required

Intuit Mailchimp


References

Ingeno, J. (2018). Software architect’s handbook. Packt Publishing.


Posted

in

by