Tags

  • AWS (7)
  • Apigee (3)
  • ArchLinux (5)
  • Array (6)
  • Backtracking (6)
  • BinarySearch (6)
  • C++ (19)
  • CI&CD (3)
  • Calculus (2)
  • DesignPattern (43)
  • DisasterRecovery (1)
  • Docker (8)
  • DynamicProgramming (20)
  • FileSystem (11)
  • Frontend (2)
  • FunctionalProgramming (1)
  • GCP (1)
  • Gentoo (6)
  • Git (15)
  • Golang (1)
  • Graph (10)
  • GraphQL (1)
  • Hardware (1)
  • Hash (1)
  • Kafka (1)
  • LinkedList (13)
  • Linux (27)
  • Lodash (2)
  • MacOS (3)
  • Makefile (1)
  • Map (5)
  • MathHistory (1)
  • MySQL (21)
  • Neovim (10)
  • Network (66)
  • Nginx (6)
  • Node.js (33)
  • OpenGL (6)
  • PriorityQueue (1)
  • ProgrammingLanguage (9)
  • Python (10)
  • RealAnalysis (20)
  • Recursion (3)
  • Redis (1)
  • RegularExpression (1)
  • Ruby (19)
  • SQLite (1)
  • Sentry (3)
  • Set (4)
  • Shell (3)
  • SoftwareEngineering (12)
  • Sorting (2)
  • Stack (4)
  • String (2)
  • SystemDesign (13)
  • Terraform (2)
  • Tree (24)
  • Trie (2)
  • TwoPointers (16)
  • TypeScript (3)
  • Ubuntu (4)
  • Home

    Dodge: 'Mixins' statements

    Published May 27, 2023 [  DesignPattern  ]

    The FilteredSocketLogger in the previous section needed its own custom __init__() method because it needed to accept arguments for both of its base classes. But it turns out that this liability can be avoided. Of course, in cases where a subclass doesn’t require any extra data, the problem doesn’t arise. But even classes that do require extra data can have it delivered by other means.

    We can make the FilteredLogger more friendly to multiple inheritance if we provide a default value for pattern in the class itself and then invite callers to customize the attribute directly, out-of-band of initialization:

    # Don’t accept a “pattern” during initialization.
    
    class FilteredLogger(Logger):
        pattern = ''
    
        def log(self, message):
            if self.pattern in message:
                super().log(message)
    
    # Multiple inheritance is now simpler.
    
    class FilteredSocketLogger(FilteredLogger, SocketLogger):
        pass  # This subclass needs no extra code!
    
    # The caller can just set “pattern” directly.
    
    logger = FilteredSocketLogger(sock1)
    logger.pattern = 'Error'
    
    # Works just fine.
    
    logger.log('Warning: not that important')
    logger.log('Error: this is important')
    
    print('The socket received: %r' % sock2.recv(512))
    
    The socket received: b'Error: this is important\n'
    

    Having pivoted the FilteredLogger to an initialization maneuver that’s orthogonal to that of its base class, why not push the idea of orthogonality to its logical conclusion? We can convert the FilteredLogger to a “mixin” that lives entirely outside the class hierarchy with which multiple inheritance will combine it.

    # Simplify the filter by making it a mixin.
    
    class FilterMixin:  # No base class!
        pattern = ''
    
        def log(self, message):
            if self.pattern in message:
                super().log(message)
    
    # Multiple inheritance looks the same as above.
    
    class FilteredLogger(FilterMixin, FileLogger):
        pass  # Again, the subclass needs no extra code.
    
    # Works just fine.
    
    logger = FilteredLogger(sys.stdout)
    logger.pattern = 'Error'
    logger.log('Warning: not that important')
    logger.log('Error: this is important')
    
    Error: this is important
    

    The mixin is conceptually simpler than the filtered subclass we saw in the last section: it has no base class that might complicate method resolution order, so super() will always call the next base class listed in the class statement.

    A mixin also has a simpler testing story than the equivalent subclass. Whereas the FilteredLogger would need tests that both run it standalone and also combine it with other classes, the FilterMixin only needs tests that combine it with a logger. Because the mixin is by itself incomplete, a test can’t even be written that runs it standalone.

    But all the other liabilities of multiple inheritance still apply. So while the mixin pattern does improve the readability and conceptual simplicity of multiple inheritance, it’s not a complete solution for its problems.

    Reference