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{

    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<IFooService>();  
     var foobar = new FooBar(mockfoo.object);
     foobar.DoFoo();
     mockfoo.Verify(foo=>foo.run())
      
   }

}

DIP/OCP equivalent testability achieved through Python:

 

class FooBar(object):

    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
                                                 

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);

}

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<TestRunner>();
  runner.RunTestsFromDir(args[0], args[1]);
}

 

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

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)

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.

Related Articles:

    Post Footer automatically generated by Add Post Footer Plugin for wordpress.

    About Ryan Svihla

    I consider myself a full stack polyglot, and I have been writing a lot of JS and Ruby as of late. Currently, I'm a solutions architect at DataStax
    This entry was posted in Dynamic Langs, Python, SOLID. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
    • Darren

      I know that dynamic languages like Ruby and Python give you things that you can’t get with languages like C#, and as a SOLID advocate those things seem important, but… why do I go cross-eyed when I read Python and Ruby examples?

    • http://www.lostechies.com/members/derick.bailey/default.aspx derick.bailey

      Hey Ryan,

      great examples, overall. however, i don’t agree with your premise that we don’t need SOLID in dynamic languages.

      i’m not sure i agree w/ your table of how dynamic languages do things where a static language would use SOLID. all of the SOLID principles still apply in object oriented, dynamic languages. they are just implemented in different ways. … my comments here are specific to Ruby, as it’s my only real exposure to dynamic/duck typing (other than javascript, but i never did any of this with js).

      SRP is still SRP. you’re right that there’s no change, here.

      OCP i still important. monkey patching can be a violation of OCP if it’s done poorly or for the wrong reasons. i’ve used OCP in my Albacore framework in a number of places and it has saved me hours of headaches, prevented ugly / leaky abstractions, and made the task in question easier to understand.

      LSP is a major concern when monkey patching. LSP is not a “technical” principle, to begin with. it’s all about semantics and meaning. changing the meaning of an object or method through monkey patching is still an LSP violation.

      ISP is still ISP, for sure. but technically everything is an object – all objects have an implicit interface.

      DIP is still valid, as well. the difference is we’re not dealing with explicit interfaces like c# or java. the client object is still expecting to find a specific method signature on the server object. the difference is that the server object does not have an explicit interface to implement. the implicit interface must still conform to the expectations of the client code, though. in this way, the client code should still own the abstraction / interface that is being called. the client code should be the one that determines when the interface definition changes, still.

      … also I don’t think your outputlib example is monkey patching. that’s more a case of a factory pattern and polymorphism, facilitating OCP/LSP. monkey patching is used to open the type (or class or instance or …) of an object and modify code in that object, at run time. you’re really just providing a different implementation of the same interface, in that example.

      -derick

    • http://www.lostechies.com/members/rssvihla/default.aspx Ryan Svihla

      @derick

      some good points but we’re in your examples disagreeing on definition issues mostly. Ruby may be different but in Python I’ve seen the term monkey patching used for class replacement.

      So score card:

      SRP: agree

      OCP: disagree ( still a bit confused here since by definition everything is open for extension AND modification even at runtime no matter what you do or how you code)

      LSP: we both agree still applies even moreso

      ISP: we both agree

      DIP: disagree

      So I’ll just touch on where we disagree OCP I covered, IoC what I’m trying to point out here is that fundamentally ALL client code can modify all code and even globally replace calls to that other code. Are there times when you want to force (I should say encourage) clients to make a choice of implementation? absolutely and I think that still applies , but the benefits of flexibility and testability provided by IoC still apply whether the client calls new or not.

      I do advocate another concept here as applies to dynamic languages only because I think there is something to be said for encouraging integration points explicit and how to go about doing that in a perscriptive fashion is needed.

      End of the day the benefits of O and the D of SOLID I’d argue we get even if we try to prevent them.

    • http://www.lostechies.com/members/rssvihla/default.aspx Ryan Svihla

      Also something I should add to that.

      I don’t mean to rid the entire SOLID acronym.

      I think SL still apply and are the primary sins I see committed in most ruby or python code I’ve looked at, however toss out “O” and “I” because I don’t think either make sense now, and for D we need to rethink what that means for these types of languages.

    • http://sharpbites.blogspot.com alberto

      Not all dynamic languages support that (i.e. PHP)

    • http://www.lostechies.com/members/rssvihla/default.aspx Ryan Svihla

      @alberto you are correct and had neglected to even think about PHP (it also has explicit interface support).

    • http://www.colourcoding.net/blog Julian Birch

      Hi Ryan, good article (even if I disagree). I’m afraid my response was rather longer than the average comment.
      http://www.colourcoding.net/Blog/archive/2009/11/20/dynamic-languages-and-solid-principles.aspx

      Julian.

    • http://blog.typemock.com Gil Zilberfeld

      Hi,

      My name is Gil Zilberfld. We’ve discussed your post on our webcast “This week in testing”. We’ll be happy if you can comment, and if you like the discussion and content, let us know. And everyone else.

      Thanks,

      Gil Zilberfeld Typemock