Logo

dev-resources.site

for different kinds of informations.

Python: Refactoring to Patterns

Published at
1/15/2025
Categories
python
refactoring
designpatterns
books
Author
douglasdcm
Author
10 person written this
douglasdcm
open
Python: Refactoring to Patterns

Man in a scaffold building a wall

Photo by Patric Ho

I’ll keep this brief to save your time — here’s the TL;DR:

This table in Python contains a list of code smells and the design patterns that address them.

class CodeSmells:
    Duplicated_Code = [
        form_template_method,
        introduce_polymorphic_creation_with_factory_method,
        chain_constructors,
        replace_one__many_distinctions_with_composite,
        extract_composite,
        unify_interfaces_with_adapter,
        introduce_null_object,
    ]
    Long_Method = [
        compose_method,
        move_accumulation_to_collecting_parameter,
        replace_conditional_dispatcher_with_command,
        move_accumulation_to_visitor,
        replace_conditional_logic_with_strategy,
    ]
    Conditional_Complexity = [  # Complicated conditonal logic
        replace_conditional_logic_with_strategy,
        move_emblishment_to_decorator,
        replace_state_altering_conditionals_with_state,
        introduce_null_object,
    ]
    Primitive_Obssession = [
        replace_type_code_with_class,
        replace_state_altering_conditionals_with_state,
        replace_conditional_logic_with_strategy,
        replace_implict_tree_with_composite,
        replace_implicit_language_with_interpreter,
        move_emblishment_to_decorator,
        encapsulate_composite_with_builder,
    ]
    # Lack of "information hiding" [Parnas]
    Indecent_Exposure = [encapsulate_classes_with_factory]
    # The logic/responsibility is sprawled in multiple places
    # (classes, methods)
    Solution_Sprawl = [move_creation_knowledge_to_factory]
    # [Fowler and Beck] Interfaces of classes different,
    # but classes are similar
    Alternative_Classes_with_Different_Interfaces = unify_interfaces_with_adapter
    # [Fowler and Beck] A class the doesn't do enough to pay itself
    Lazy_Class = [inline_singleton]
    Large_Class = [
        replace_conditional_dispatcher_with_command,
        replace_state_altering_conditionals_with_state,
        replace_implict_tree_with_composite,
    ]
    Switch_Statements = [  # Complicated switches
        replace_conditional_dispatcher_with_command,
        move_accumulation_to_visitor,
    ]
    # Code that do the same with different types or quantity of data
    # (similar to duplication)
    Combination_Explostion = [replace_implicit_language_with_interpreter]
    # The same problem being solved in many ways in the system
    # (similar to duplication)
    Oddball_Solutions = [unify_interfaces_with_adapter]
Enter fullscreen mode Exit fullscreen mode

Refactorings

After nearly a year of effort, I’ve finally completed my self-imposed goal of writing all the refactoring examples from the book Refactoring to Patterns by Joshua Kerievsky in Python. This book broadened my understanding of how to apply design patterns in production code.

Each example includes a brief explanation of the original code and its context, followed by the refactored code and the benefits gained through the refactoring. For instance, the refactoring to “Compose Method” transforms difficult-to-read code into a simple, streamlined implementation.

Lets get this example and examine the original code.

# Original code
# It is not easy to understand the code
def add(element):
    readonly = False
    size = 0
    elements = []
    if not readonly:
        new_size = size + 1
        if new_size > len(elements):
            new_elements = []
            for i in range(size):
                new_elements[i] = elements[i]
            elements = new_elements
        size += 1
        elements[size] = element
Enter fullscreen mode Exit fullscreen mode

It is possible to see that the code is not easy to understand. It has many nested conditions and loops. Now lets go to the refactored code.

# Code Refactored
# The new code has meaningfull names for blocks of code and is not nested.
# The Compose Method is a refactoring to simplificate the code
def at_capacity(new_size, elements):
    new_size > len(elements)


def grow(size):
    new_elements = []
    for i in range(size):
        new_elements[i] = elements[i]
    elements = new_elements


def add_elements(elements, element, size):
    size += 1
    elements[size] = element


def add_refac(element):
    readonly = False
    if readonly:
        return
    if at_capacity:
        grow()
    add_elements(element)
Enter fullscreen mode Exit fullscreen mode

The idea of the refactoring is reduce the complication with meaningful methods and remove the nested branches. Notice it was necessary to extract blocks of code to methods.

While working through the book and writing the examples, I had to interpret UML diagrams and understand the mechanics in detail. This required intense focus and mental effort. Many times, I had to rebuild the examples from scratch because converting code from Java to Python is not straightforward. Native Python doesn’t support cyclic imports, constructor overloads, or interfaces well, so some adaptations were necessary. I added comments to these areas to help future consultation of the code.

Through this process, I realized my previous understanding of design patterns was mostly theoretical and limited to trivial scenarios. For example, while I understood that “Polymorphism” addresses development problems, the book showed its application in test automation by abstracting the setup phase and reusing the remaining test implementation.

Here are both versions of the code. The difference between the original and new code is the setup of the test.

# Original code
# Similar methods differs from the object instantiation.
# All the rest is the same
class TestCase:
    pass


class DOMBuilder:
    def __init__(self, orders) -> None:
        pass

    def calc(self):
        return 42


class XMLBuilder:
    def __init__(self, orders) -> None:
        pass

    def calc(self):
        return 42


class DOMTest(TestCase):
    def run_dom_test(self):
        expected = 42
        builder = DOMBuilder("orders")  # different object created
        assert builder.calc() == expected


class XMLTest(TestCase):
    def run_xml_test(self):
        expected = 42
        builder = XMLBuilder("orders")  # different object created
        assert builder.calc() == expected


# Code refactored
# The instantiation of the DOMBuilder or XMLBuilder is the only difference
# in both tests.
# It was created an OutputBuilder like an interface for both classes
# (it is not necessary given that Python uses duck type).
# In TestCase a new method called "create_builder" was introduced to be
# implemented by the children classes.
# This is the step executed in runtime for each type of test. This is the
# polymorphism. When both tests (DOMTest and XMLTest) are executed,
# the instance returned from the "create_builder" depends on the
# implementation. Is can be DOMBuilder or XMLBuilder.
class OutputBuilder:
    def calc(self):
        raise NotImplementedError()


class DOMBuilderRefac(OutputBuilder):
    def calc(self):
        return 42


class XMLBuilderRefac(OutputBuilder):
    def calc(self):
        return 42


class TestCaseRefac:
    def create_builder(self):
        raise NotImplementedError()

    def run_test(self):
        expected = 42
        builder = self.create_builder()  # different object created
        assert builder.calc() == expected


class DOMTestRefac(TestCaseRefac):
    def create_builder(self) -> OutputBuilder:
        return DOMBuilderRefac()


class XMLTestRefac(TestCaseRefac):
    def create_builder(self):
        return XMLBuilderRefac()


def run():
    dom_tc = DOMTestRefac()
    dom_tc.run_test()

    xml_tc = XMLTestRefac()
    xml_tc.run_test()
Enter fullscreen mode Exit fullscreen mode

The “Visitor” pattern was the most difficult for me to understand. I read about the pattern in the Design Patterns book before attempting the refactoring. It was only after seeing the original (unrefactored) code being transformed into the new version that I realized the pattern isn’t as complex as it initially seems. Essentially, the pattern decouples classes from their methods. Again, both codes for comparison. The implementation of the pattern is “by the book”.

# Original code
# The TextExtractor has lots of conditons to handle Nodes, like StringNode
# The idea ofthe rectoring is distribute the logic into Visitor classes


# Interface
class Node:
    pass


class LinkTag(Node):
    pass


class Tag(Node):
    pass


class StringNode(Node):
    pass


class TextExtractor:
    def extract_text(self, nodes: list[Node]):
        result = []
        for node in nodes:
            if isinstance(node, StringNode):
                result.append("string")
            elif isinstance(node, LinkTag):
                result.append("linktag")
            elif isinstance(node, Tag):
                result.append("tag")
            else:
                result.append("other")
        return result


# Code refactored
# Interface (the visitor)
class NodeVisitorRefac:
    def visit_link_tag(self, node):
        return "linktag"

    def visit_tag(self, node):
        return "tag"

    def visit_string_node(self, node: object):
        return "string"


class NodeRefac:
    def accept(self, node: NodeVisitorRefac):
        pass


class LinkTagRefac(NodeRefac):
    def accept(self, node: NodeVisitorRefac):
        return node.visit_link_tag(self)


class TagRefac(NodeRefac):
    def accept(self, node: NodeVisitorRefac):
        return node.visit_tag(self)


class StringNodeRefac(NodeRefac):
    def accept(self, node: NodeVisitorRefac):
        return node.visit_string_node(self)


# The concret visitor
class TextExtractorVisitorRefac(NodeVisitorRefac):
    def extract_text(self, nodes: list[NodeRefac]):
        result = []
        for node in nodes:
            result.append(node.accept(self))
        return result


def run_refac():
    # The original object calling its method
    result1 = TextExtractor().extract_text([StringNode()])
    # The new object accepting visitors
    result2 = TextExtractorVisitorRefac().extract_text([StringNodeRefac()])
    return result1, result2

Enter fullscreen mode Exit fullscreen mode

Conclusion

I highly recommend the book to everyone. The first time I read it, I found it boring and difficult to grasp the concepts just by following static code examples. However, when you actively write the code, the ideas gradually come to life. Errors will occur, and addressing them requires understanding the underlying concepts. This process transforms theory into practice and solidifies your knowledge.

books Article's
30 articles in total
Favicon
Lessons from A Philosophy of Software Design
Favicon
Python: Refactoring to Patterns
Favicon
Timeline of key events in Nvidia's history
Favicon
Indian Law For A Common Man—A Guide to Legal Literacy
Favicon
[Boost]
Favicon
Read later
Favicon
The Web Dev's Guide to Freelancing is Now Available! 🎉
Favicon
Middleman.Asia- The lawyer at your fingertips
Favicon
A promising introduction for both coding novices and experienced developers!😊👩‍🎓
Favicon
Book Review: Designing Data-Intensive Applications
Favicon
[Boost]
Favicon
Top 10 Books for Boosting Efficiency, Productivity, and Performance
Favicon
[Boost]
Favicon
A Simple Guide to Understanding Indian Law
Favicon
WeDidIt - an NGO that refuses donations and supports empowerment.
Favicon
[Boost]
Favicon
How waking up at 5 am can help you build your next big app
Favicon
Book Recommendations for Personal Growth and Success
Favicon
Text to speech (book to audiobook)
Favicon
Correlating The Book, “Indian Law For A Common Man” With AI
Favicon
5 Killer Habits: Be a Rebel
Favicon
10 Software Engineering Books Developers Should Read in 2025
Favicon
Share the most interesting book you have read
Favicon
Recommended books
Favicon
Лучшие книги фэнтэзи
Favicon
Book Review: Soft Skills: The Software Developer's Life Manual by John Sonmez
Favicon
2025 New Book Launched !Web Automation Testing with Playwright
Favicon
The Best Academy
Favicon
[Boost]
Favicon
Влияние литературы на формирование личности

Featured ones: