Search code examples
pythonpython-2.7loose-coupling

What is the right way to do "loose coupling" in python?


I wrote some code to get data using pySerial as below.
My class is depend on serial class which doesn't meet the "loose coupling" rule.
Should I use interface to decouple my class?
Thanks a lot for your instruction.

import serial

class ArduinoConnect:  

    def __init__(self):
        pass

    def serial_connect(self, serial_port, serial_baudrate):

        self._serial_port = serial_port
        try:
           self.ser = serial.Serial(
                port=self._serial_port,
                baudrate=serial_baudrate,
                parity=serial.PARITY_NONE,
                stopbits=serial.STOPBITS_ONE,
                bytesize=serial.EIGHTBITS,
            )
        except serial.serialutil.SerialException, e:
            print str(e)

    def serial_disconnect(self):
        self.ser.close()

    def get_quaternion(self, number_of_data=50):

        buff = []
        self.ser.write('q')
        self.ser.write(chr(number_of_data))
        for j in range(number_of_data):
            in_string = self.ser.readline()
            buff_line = in_string.split(",")
            buff_line.pop()
            buff_line = self.hex_to_quaternion(buff_line)
            buff.append(list(buff_line))
        return buff

    def hex_to_quaternion(self, list_of_hex=None):
        #......
        pass

arduino = ArduinoConnect()
arduino.serial_connect(serial_port="COM5", serial_baudrate=115200)
print arduino.get_quaternion()
arduino.serial_disconnect()

I adjusted my code as recommended.
DI help to separate the serial process,and a factory method help to encapsulate the DI process.
Is there anything else I could do to meet the "loose coupling" rule?
Thanks for your help.

import serial

class ArduinoConnect:
    def __init__(self, serial_to_arduino):
        self._serial_to_arduino = serial_to_arduino

    def get_quaternion(self, number_of_data=50):
        buff = []
        self._serial_to_arduino.write('q')
        self._serial_to_arduino.write(chr(number_of_data))
        for j in range(number_of_data):
            in_string = self._serial_to_arduino.readline()
            buff_line = in_string.split(",")
            buff_line.pop()
            buff_line = self.hex_to_quaternion(buff_line)
            buff.append(list(buff_line))
        return buff

    def hex_to_quaternion(self, list_of_hex):
        ......

    def __getattr__(self, attr):
        return getattr(self._serial_to_arduino, attr)


class SerialToArduino:
    def __init__(self):
        pass

    def serial_connect(self, serial_port="COM5", serial_baudrate=115200):
        self._serial_port = serial_port
        try:
            self.ser = serial.Serial(
                port=self._serial_port,
                baudrate=serial_baudrate,
                parity=serial.PARITY_NONE,
                stopbits=serial.STOPBITS_ONE,
                bytesize=serial.EIGHTBITS,
            )
        except serial.serialutil.SerialException, e:
            print str(e)

    def serial_disconnect(self):
        self.ser.close()

    def readline(self):
        return self.ser.readline()

    def write(self, data):
        self.ser.write(data=data)


def get_ArduinoConnect():
    'factory method'
    return ArduinoConnect(serial_to_arduino=SerialToArduino())


arduino = get_ArduinoConnect()
arduino.serial_connect(serial_port="COM5", serial_baudrate=115200)
print arduino.get_quaternion()
arduino.serial_disconnect()

Solution

  • I can think of 2 possible solutions

    1. Implement an "adapter" to expose methods to the main class and hide the implementation of Serial. So that your main class can avoid having dependency on the concrete class Serial
    2. Do Dependency Injection, something likes this

      def serial_connect(self, engine, serial_port, serial_baudrate)

    Update for 1: I referred to http://en.wikipedia.org/wiki/Adapter_pattern which is commonly used when you want to separate the concrete implementation from the abstraction. Think of it as the travel plug adapter.

    It is particularly useful for language like Java with strict interface and everything. In your case, because in Python we don't have "interface", you can simulate it by using an abstract class

    class AbstractAdapter():
          def serial_connect(self, serial_port="COM5", serial_baudrate=115200):
              raise("Needs implementation")
          # do the same thing for the rest of the methods
    

    Then in ArduinoConnect, you can check for the type

    def __init__(self, serial_to_arduino):
        if not isinstance(serial_to_arduino, AbstractAdapter):
            raise("Wrong type")
    

    This forces your serial_to_arduino to extend AbstractAdapter which enforces the implementation of all abstract methods hence an adapter.

    This might not be the most "pythonic" way to do things, but from OOP point of view, you can do it that way to have the highest level of loose coupling (In my opinion)

    P/s: Actually, I think the correct pattern in this case should be Strategy, both of them are pretty similar in term of implementation but they are meant for different purposes. You can read more about some patterns like Strategy, Proxy, Command, Mediator which are often used to achieve loose coupling