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
Please enter at least three characters to search
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

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Supercharging Pytest: Integration With External Tools
  • Pydantic: Simplifying Data Validation in Python
  • Bridging Graphviz and Cytoscape.js for Interactive Graphs
  • Router4j: A Free Alternative to Google Maps for Route and Distance Calculation

Trending

  • How to Build Scalable Mobile Apps With React Native: A Step-by-Step Guide
  • Artificial Intelligence, Real Consequences: Balancing Good vs Evil AI [Infographic]
  • Kubeflow: Driving Scalable and Intelligent Machine Learning Systems
  • Measuring the Impact of AI on Software Engineering Productivity
  1. DZone
  2. Coding
  3. Tools
  4. Refactoring Design Patterns in Python

Refactoring Design Patterns in Python

After a year, I finally converted all examples from the book Refactoring to Patterns by Joshua Kerievsky to Python. I explain some examples in this article.

By 
Douglas Cardoso user avatar
Douglas Cardoso
·
Jan. 23, 25 · Analysis
Likes (0)
Comment
Save
Tweet
Share
2.1K Views

Join the DZone community and get the full member experience.

Join For Free
This table in Python contains a list of code smells and the design patterns that address them.
Python
 
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]


The Journey

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.

Let's take this example and examine the original code.

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


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

Python
 
# 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)


The idea of the refactoring is to 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 was not straightforward. Native Python doesn’t support cyclic imports, constructor overloads, or interfaces well, so some adaptations were necessary. I added comments on these areas to help with future consultations on 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.

Python
 
# 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()


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 are for comparison. The implementation of the pattern is "by the book."

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


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.

Python (language) Data Types Distributed design patterns Tool

Published at DZone with permission of Douglas Cardoso. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Supercharging Pytest: Integration With External Tools
  • Pydantic: Simplifying Data Validation in Python
  • Bridging Graphviz and Cytoscape.js for Interactive Graphs
  • Router4j: A Free Alternative to Google Maps for Route and Distance Calculation

Partner Resources

×

Comments
Oops! Something Went Wrong

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
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!