0

A lots of examples of Ruby composition are like this:

class GUI
  def get_input
    gets.chomp
  end
end

class Computer
  def initialize
    @ui = GUI.new
  end

  def get_input
     @ui.get_input
  end
end

But, I thought this could be

module GUI
  module_function

  def get_input
    gets.chomp
  end
end

class Computer
  def initialize
    @ui = GUI
  end

  def get_input
    @ui.get_input
  end
end

too. So then why do we use Classes not Modules here?

Todoroki
  • 515
  • 4
  • 12
  • 5
    https://stackoverflow.com/questions/151505/difference-between-a-class-and-a-module or https://lh4.googleusercontent.com/e_Eml6aYg1udItOLjQCzUKF1L2K1JcjyZTnzYwcP7A=w1530-h800-no or http://www.vikingcodeschool.com/professional-development-with-ruby/classes-vs-modules might help but basically a `Class` is a special kind of `Module` that can be instantiated. A `Class` is a stateful representation of an "Object" where as a `Module` is more of a stateless collection of methods (generally implemented for reusability and shared functionality) – engineersmnky Aug 30 '17 at 17:23
  • thanks @engineersmnky, so for in composition we use functions of others, should we use `module_function`-ed `Module`s? – Todoroki Aug 30 '17 at 17:31
  • Slight addition to my original comment. Think about if `GUI` was to store the user input e.g. `def get_input; @user_inputs << gets.chomp;end` if this was a `Module` the user inputs would be shared across all implementations of `GUI` or in this case all `Computers`. (probably not what you want) but as a class each `Computer`s `GUI` would store the inputs separately in its own instance of `GUI`. – engineersmnky Aug 30 '17 at 17:33
  • Generally `Module` methods are "included" in a `Class` to add commonality or functionality to that class. I have honestly never used a `module_function` in any code base I have ever written. – engineersmnky Aug 30 '17 at 17:36

1 Answers1

0

This isn't a really good example of composition because the GUI doesn't make use of internal state. If you did something like this, that would be a more clear case of composition:

class GUI
  def initialize(prompt)
    @prompt = prompt
  end
  def get_input
    puts @prompt
    gets.chomp
  end
end

class Computer
  def initialize
    @ui = GUI.new("enter text")
  end

  def get_input
     @ui.get_input
  end
end

With this code, each instance of computer has its own instance of GUI, with its own bound behavior (the value of the prompt text).

This could also be done with modules, as follows:

module GUI
  def get_input
    puts @prompt
    gets.chomp
  end
end

class Computer
  include GUI
  def initialize
    @prompt = "enter text"
  end
end

As to why the class implementation might be considered better than the module one - well, there are pros and cons. With the module, the Computer class can be shorter and doesn't need to define the get_input method itself because it's included from the mixin. However, the class approach is more explicit and probably more maintainable in the long run.

There are at least two reasons for this. First of all, GUI defines its dependencies explicitly. Initialize will raise an error if the wrong number of arguments are passed. But instance variables will be nil if they are unset, so if GUI gets used somewhere else, that detail could be missed. Second, defining wrapper methods like def get_input; @ui.get_input; end; might seem redundant but can actually be useful sometimes. It gives you a way to make Computer#get_input slightly different than GUI#get_input, if you so desired.

Bringing back your original example here. Like I said, it's not really a good use of composition. Since the GUI class has no dependencies or internal state, it can be self-contained and doesn't need to be instantiated either:

class GUI
  def self.get_input
    gets.chomp
  end
end

class Computer
  def get_input
    GUI.get_input
  end
end

Note that you can change the class GUI to a module and it would have the same effect. You could also use module_method instead of def self. as described here: https://idiosyncratic-ruby.com/8-self-improvement.html

max pleaner
  • 26,189
  • 9
  • 66
  • 118
  • 1
    The second example is distasteful in my opinion as it creates a loose binding between the `GUI` implementation and the including class. I say loose because obviously `puts nil` has limited impact which would be the case in the event `@prompt` was not set but I don't believe that a `Module` should be this dependent on the internals of the class including it. I would actually consider defining `get_input` as `def get_input(prompt=nil); puts prompt if prompt; gets.chomp; end` as this expands functionality drastically for things like `def name; @name = get_input("What is your name?");end` – engineersmnky Aug 30 '17 at 17:43
  • Overall this is nice, but I've got to agree with @engineersmnky in that the second example is pretty dirty. Setting an instance variable the other thing magically picks up on is not good encapsulation. – tadman Aug 30 '17 at 20:01
  • Thanks for the answer and the comments. I've totally forgotten the idea of storing states in an instance... my bad. And I too agree with @engineersmnky here, but with a slightly different view: Aren't mix-ins more similar to inheritance rather than composition? That is the reason I used `module_function` in the question. – Todoroki Aug 31 '17 at 09:30