1. 7
  1.  

  2. 3

    Due to duck typing design, Interface Segregation and Dependency Inversion principle are not relevant in Ruby.

    Sorry, but I strongly disagree with you on this statement. I understand some ruby developers have an aversion to any type system or any formality, but being highly dependent on DT can cause you serious problems. Specially when you’re working with a large codebase, that has been around for a while and abuse on monkey patching.

    Here are two good examples where these principles can be applied successfully in ruby and help you bring some sanity to your projects.

    http://rubyblog.pro/2017/07/solid-interface-segregation-principle http://rubyblog.pro/2017/07/solid-dependency-inversion-principle

    1. 1

      Thanks for your feedback.

      But, this is not ISP.. The example doesn’t follow the principles:

      • Clients should not be forced to depend on methods that they do not use. OK
      • Many client specific interfaces are better than one general purpose interface. KO

      This example show a FeeCalculator that provides 2 methods..

      1. 2

        ISP definitely does apply to Ruby, or any other language, because the principle in itself is not dependent on language constructs. It’s easier to visualize in statically typed languages because it’s explicit, it’s something you need to write. When working with dynamic languages, the construct doesn’t exist explicitly, but you’re still making design decisions about how the classes interact with each other. Understanding and reasoning about how much one class know about another in a given context, is exactly what ISP is about.

        For example, in Ruby’s standard library, we have a File and a StringIO class. They each have their own methods, but they also share a smaller common interface. That is not accidental, the library designers made a conscious decision to have similar methods so that the class can be used interchangeably in specific contexts.

        As a user of those libraries, you also have an option. You can use methods on those classes that are specific to each one, or you can organize your code in a way where you pick a very small interface that you’re gonna depend on. If you only use methods that are available on both classes, you can pass any of those classes into your component and it will just work.

        The main difference on dynamic languages is that you don’t have a fixed set of interfaces that are provided by the library authors. You have a number of interfaces that is equal to the combination of all methods available in that class API.

    2. 3

      I know it’s just an example, but regarding inheriting from Array: Beware subclassing Ruby core classes specifically uses subclassing from Array as an example that has weird side effects.

      1. 1

        Yes. Inheriting like that, although is technically a valid case of open/closed principle, it’s a bad practice on it’s own. Not only that, it violates the other SOLID principle called Liskov substitution.

        Usually, when referring to open/closed, a much better example would be based on composition instead of inheritance. There are many patterns that are composition-based that correctly represent the principle:

        • Using the adapter or the command pattern, to delegate specific behaviors to specific classes. Adding a new behavior only requires adding another class.
        • Observers, which allows to easily extend the behavior by simply adding a new class.
        • The use of dependency injection and a common interface, which allows to change the behavior of the system by injecting a new class.
        • Etc. This is not exclusive to established design patterns, but rather a trait that most good patterns share. Overall, open/closed principle is about architecture and how the components fit together.
      2. 2

        You can’t say you have five short examples when you only have three :P

        Also, I don’t think duck typing means DI is irrelevant. Dependency Inversion roughly says that abstraction should flow strictly downward: classes should depend* on more abstract classes, not less. If Sensor depends on SensorEventLogger, then HeatSensor shouldn’t depend on just EventLogger.

        • Depends is different from “uses” here. Class A uses class B if there’s a method in A that, if called, leads to a method in B being called. Class A uses class B if class A’s correctness requires class B to also be correct.
        1. 2

          Maybe the Liskov substitution principle has more idiomatic examples but I think this one actually breaks with other principles like DRY

          class Role
            def to_s
              self.class.name.lower
            end
          end
          

          Also why do you provide examples but do not provide hints as to why they are interesting and how people come up with them?