PPOOD

Pt. 2 Dependencies

Recognizing Dependencies

Powerful and Perilous Collaboration

Your design challenge is to manage dependencies so that each class has the fewest possible; a class should know just enough to do its job and not one thing more.
from dataclasses import dataclass


@dataclass
class Wheel:
    rim: int
    tire: int

    def diameter(self):
        return self.rim + (self.tire * 2)


class Gear:
    def __init__(self, chainring, cog, rim, tire):
        self._chainring = chainring
        self._cog = cog
        self._rim = rim
        self._tire = tire

    def ratio(self):
        return self._chainring / self._cog

    def gear_inches(self):
        wheel = Wheel(self._rim, self._tire)
        return self.ratio() * wheel.diameter()
from dataclasses import dataclass


@dataclass
class Wheel:
    rim: int
    tire: int

    def diameter(self):
        return self.rim + (self.tire * 2)


class Gear:
    def __init__(self, chainring, cog, rim, tire):
        self._chainring = chainring
        self._cog = cog
        self._rim = rim
        self._tire = tire

    def ratio(self):
        return self._chainring / self._cog

    def gear_inches(self):
        wheel = Wheel(self._rim, self._tire)
        return self.ratio() * wheel.diameter()

The name of another class.

The arguments that a message requires.

Argument order that a message requires.

Message name on object other than self.

Left unchecked, unmanaged dependencies cause an entire application to become an entangled mess. A day will come when it’s easier to rewrite everything than to change anything.

Writing Loosely Coupled Code

Injecting Dependencies

    def gear_inches(self):
        wheel = Wheel(self._rim, self._tire)
        return self.ratio() * wheel.diameter()

Knowing the name of something.

Coupling Consequences:

  1. Forced change when the `Wheel` class changes it's name.
  2. `Gear` is saying it only works with instances of `Wheel` and refuses to collaborate with any other type of object.

Gear becomes less useful it knows more about other objects; if it knew less it could do more.

class Gear:
    def __init__(self, chainring, cog, wheel):
        self._chainring = chainring
        self._cog = cog
        self._wheel = wheel

    def ratio(self):
        return self._chainring / self._cog

    def gear_inches(self):
        return self.ratio() * self._wheel.diameter()

Isolating external messages

    def gear_inches(self):
        return self.ratio() * self._wheel.diameter()

    # imagine there were many more uses of `self._wheel.diameter()`

Knowing about messages to external objects.

Coupling Consequences:

  1. `Gear` still has knowledge that another object exists named `_wheel` which responds to `diameter()`.
  2. If the message is used many places, changes will be forced in every location.
class Gear:
    def __init__(self, chainring, cog, wheel):
        self._chainring = chainring
        self._cog = cog
        self._wheel = wheel

    def _diameter(self):
        return self._wheel.diameter()

    def ratio(self):
        return self._chainring / self._cog

    def gear_inches(self):
        return self.ratio() * self._diameter()

    # Imagine other messages that now use `self._diameter()`.
    # This helps isolate where the `_wheel` object is known.

Removing argument

order dependence

gear = Gear(52, 11, Wheel(26, 1.5))

Knowing argument ordering.

Even knowing how to send a message is a form of coupling.

class Gear:
    def __init__(**kwargs):
        self._chainring = kwargs["chainring"]
        self._cog = kwargs["cog"]
        self._wheel = kwargs["wheel"]

g1 = Gear(wheel=Wheel(26, 1.5), chainring=52, cog=11)

Now argument order does not matter.

def divide(**kw):
    return kw["a"] / kw["b"]

# OH COME ON!
divide(b=2, a=3)
divide(3, 2)

Downsides:

  • Adds verbosity
  • Can make code harder to read/edit

It's up to us to decide when this adds value, and when it hurts.

Good spots:

  • When working with a long parameter list
  • When working with unstable, rapidly changing API.
  • Building a framework for use by others.
def csv_reader(
    csvfile,
    sep=",",
    line_terminator="\r\n",
    quote_char='"',
    skip_initial_space=False
):
    pass

r = csv_reader(open("users.csv"))
r = csv_reader(open("users.csv"), sep='"', quote_char=",")

Happy medium:

Message takes a few very stable and predictable arguments, and some number of less stable and order-independent defaulted arguments.

In case you run into a comma-quoted quote-separated data file.

Understanding the causes of change

Concretions and Abstractions

Abstraction - From Mirriam-Webster:

  • disassociated from any specific instance
  • difficult to understand
  • expressing a quality apart from an object
from dataclasses import dataclass


@dataclass
class Wheel:
    rim: int
    tire: int

    def diameter(self):
        return self.rim + (self.tire * 2)


class Gear:
    def __init__(self, chainring, cog, wheel):
        self._chainring = chainring
        self._cog = cog
        self._wheel = wheel

    def ratio(self):
        return self._chainring / self._cog

    def gear_inches(self):
        return self.ratio() * self._wheel.diameter()


"""
When we injected ``Wheel`` into ``Gear`` and made it able
to depend on any object that could respond to ``diameter``,
we made an abstract interface. It is an abstract idea to
make ``Gear`` work with a "diameter-able" object interface
instead of a concrete instance of ``Wheel``.
"""

Our programs should be philosophers. It doesn't matter if it's an `Apple` or an `Orange`; all that matters to the `rabid_hunger.py` program is that it adheres to the `IEatable` interface.

Dealing with big classes.

The pain of big classes:

  • More objects to know about, more reason and opportunities to break.
  • Has many reasons to change, but is hard to change due to complexity.
  • All paths lead to the big class.

 

How to fix:

  • It needs to be split up into multiple smaller classes with fewer dependencies and dependents, and clear single responsibility.

Dependent-Laden Classes

Comments

&

Questions

 

PPOOD - Dependencies

By Will Vaughn

PPOOD - Dependencies

  • 420