“The Ruby way” (mixins and class reopening) vs. dependency injection

前端 未结 5 1201
生来不讨喜
生来不讨喜 2021-02-07 06:01

In studying mixins vs. dependency injection, I often hear the phrase \"the Ruby way.\" Often developers say something along the lines of

Ruby lets you reo

5条回答
  •  梦谈多话
    2021-02-07 06:36

    Well just because we can reopen classes in Ruby doesn't mean we always have to, you can think of reopening classes as the method of last resort. You have a library which does everything you need except for one method, rather than forking the whole library patching it and using your fork, you can simply reopen the class, redefine the method and you're in business again. This is not something you would do willy-nilly, but having the ability to do this is extremely useful.

    Having said all of that, in Ruby we have a concept that can almost always be a good substitute for dependency injection - duck typing. Since there is no type checking you can pass any object into a function and as long as the object has the methods that the function would expect, everything will work fine.

    Let us look at your example - it is not really class that is conducive to dependency injection, you would not write it like this in Java, for example, if you wanted to inject some dependencies. You can only inject through the constructor or through getters and setters. So let's rewrite this class in that way:

    class Foo
      def initialize(logger)
        @logger = logger
      end
    end
    

    Much better we can now inject/pass in a logger into our Foo class. Let's add a method that would use this logger to demonstrate:

    class Foo
      def initialize(logger)
        @logger = logger
      end
    
      def do_stuff
        @logger.info("Stuff")
      end
    end
    

    In java if you wanted to create Foo objects with different types of loggers, all those loggers would have to implement the same interface in very literal sense (e.g. public class logger implements Loggable), or at least be child classes. But in Ruby as long as the object has an info method that accepts a string, you can pass it into the constructor and Ruby keep chugging along merrily. Let's demonstrate:

    class Logger
      def info(some_info)
      end
    end
    
    class Widget
      def info(some_widget_info)
      end
    end
    
    class Lolcat
      def info(lol_string)
      end
    end
    
    Foo.new(Logger.new).do_stuff
    Foo.new(Widget.new).do_stuff
    Foo.new(Lolcat.new).do_stuff
    

    With all 3 of the above instances of the Foo class calling the do_stuff method, everything will work fine.

    As you can see from this example adhering to the principles of OO design is still important, but Ruby is somewhat less restrictive about what it will accept, as long as the right methods are there everything will be fine.

    Depending on how you look at it duck typing either makes dependency injection totally irrelevant or makes it more powerful than ever.

提交回复
热议问题