Take 2: Why we use SOLID in static languages and how we get the same functionality for cheap in dynamic languages


One of the things we do pretty well at Los Techies is explaining SOLID principles and why they make our code more maintainable and if you’re not familiar with our work on SOLID,  read Chad Meyer’s post to get an understanding on the Los Techies perspective. Now that you’ve taken a bit of time to read that the following chart takes SOLID’s benefits and then shows how they apply to dynamic languages:

 

Principle Primary Benefit Secondary Benefit What dynamic langs do to replace said principle
Single Responsibility Principle (SRP) Classes are in understandable units Testability Nothing
Open-Closed Principle (OCP) Flexibility Guidance can override anything globally (monkey patch)
Liskov Substitution Principle (LSP) Not bleeding subclass specific knowledge everywhere Least surprise Nothing
Interface Segregation Principle (ISP) Interfaces are in understandable units Less work for interface implementation everything’s an interface (duck typing)
Dependency Inversion Principle(DIP) Flexibility Testability can override anything globally (monkey patch)

 

The above is my opinion and I’m sure people will beat me to death because I’m just some dude not many people know about,  but I think I’ve got a grasp of what SOLID means in C# and I’ve grappled with it from a practical perspective.

You’ll note when it comes to LSP and SRP dynamic languages really give you nothing at all, but OCP, ISP, and DI benefits wise are supplanted by language features in python, ruby, etc.  How does this happen? In the case of ISP and “duck typing” this should be pretty obvious, but in the cases of OCP, DIP

 

DIP/OCP testability achieved through C#:

 

public class FooBar{</p>

    private IFooService _service;

    public FooBar(IFooService service){
                       
          _service = service;
    }

    public void DoFoo(){
        _service.run();
    }
}

//testcode

[TestFixture]

public class FooBarSpec{
  
  [Test]

  public void should_call_fooservice(){
     var mockfoo = new Mock();  
     var foobar = new FooBar(mockfoo.object);
     foobar.DoFoo();
     mockfoo.Verify(foo=>foo.run())
      
   } </p>

} </div> </div>

DIP/OCP equivalent testability achieved through Python:

 

class FooBar(object):</p>

    def DoFoo(self):
        service = FooService()
        service.run()

#test code
import unittest

class TestFooBar(unittest.TestCase):

    def runmock(self):
        self.fooservice_run_was_called = True

    def test_do_fooservice_is_called(self):
        originalrun = FooService.run #storing the original method in a variable 
        FooService.run = self.runmock  # I’m globally overriding the method call FooService.run AKA Monkey Patch
        FooBar().DoFoo()
        FooService.run = originalrun #globally put it back to where it was AKA Monkey Patch
        assert self.fooservice_run_was_called
                                                  </div> </div>

Now rightfully you may say the Python version has a few more lines of code, and it certainly can be considered ugly…but don’t you see that I replaced an entire mocking library in a few lines of code.  There are also mocking libraries and monkey patch libraries for Python that would shorten my line of code count further.  Testability and flexibility dovetail, but the global accessibility of class definitions is understandably hard to grasp for the static mindset (it took me months), so we’ll continue on to flexibility:

public interface IOutput{
  
  void save(string fileloc, ResultObjects[] results);</p>

}

public class XmlOutput:IOutput {
 
   public void save(string fileloc, ResultObjects[] results){
    // place xml specific code here
}

public class HtmlOutput: IOutput {
 
   public void save(string fileloc, ResultObjects[] results){
    // place html specific code here
}

public class TestRunner {
 
  private IOutput _output;
  private ITests _tests;
  public StoreTestResults(IOutput output, ITests tests){
    _output = output;
    _tests = tests;
  }
  public void RunTestsFromDir(string testdirectory, string outfile){
  
    var results = _tests.GetResults(testdirectory);
    _output.save(outputfile, results)
  }

//later on we use IoC containers to make this chain easy 

pubilc static void main(string[] args){

  // imagine contextual resolution is happening intelligently 
 // on the resolver because someone did some cool stuff in windsor
  var runner = testcontainer.Resolve();
  runner.RunTestsFromDir(args[], args[1]);
} </div> </div>

 

In Python with “monkey patching” everything is “Open For Extension” and dependencies can “always” be injected. A code sample based on my last blog post but extended to include some more complete client code follows:

 

#in module outputlib
class Output(object):
  def save(fileloc, results):
    pass</p>

class XmlOutput(object):
  def save(fileloc,results):
     “””xml specific logic here”””
     pass

class HtmlOutput(object):
  def save(fileloc,results):
     “””xml specific logic here”””
     pass

class TestRunner(object):

  def run_tests_from_dir(testdirectory, outfile):
    tests = Tests()
    output = Output()
    output.save(  outfile  , tests.get_results(testdirectory)

#rest of the script

import sys
import outputlib as o

testdirectory = sys.argv[1]
outfile = sys.argv[2]
if outfile.endswith(“xml”):
  o.Output = o.XmlOutput #globally override calls to Output with XmlOutput. AKA Monkey Patch
elif outfile.endswith(“html”):
  o.Output = o.HtmlOutput #globally override calls to Output with HtmlOutput AKA Monkey Patch
runner = o.TestRunner()
runner.run_tests_from_dir(testdirectory, outfile) </div> </div>

Again this is longer code than the c# example.. but we’ve replaced an entire IoC container and whatever custom code we would have had to add to create custom dependency resolver for selecting HTML or XML output options has now been replaced by a simple if else if conditional at the beginning of the code base execution. Now all calls in this runtime, from any module, that reference the Output class will use XmlOutput or HtmlOutput instead, all with “monkey patching”.

Hopefully, from here the rest of the dominos will fall into place once you get the concepts above, and the things we rely on SOLID for are in some cases already there in dynamic languages. Consequentially, my dynamic code looks (now) nothing like my static code because I no longer have to use DI/IoC to achieve my desired flexibility and access to use proper unit tests.

I recant my IoC! IoC containers in dynamic languages are silly.