adamszabo wrote:Now the question is, which is better?
The Class is better. That way of using a Module is just not what Modules are meant to be used for, and is poor Ruby code - the example is precisely the kind of problem for which Classes and their instances were invented. Effectively, you are trying to force an object-oriented language (Ruby) to be an imperative language (e.g. DSP-code, Pascal). Please, forget Modules for the time being - by allowing you to keep thinking in terms of "functions" instead of "objects", they are seriously leading you along the wrong path for learning Ruby.
adamszabo wrote:But will the class use more memory since it creates new square and the module only draws the existing square?
It will make very little difference to memory usage, and the Module is probably less CPU efficient.
Class instances ("objects") do two things: 1) They act as a container for a bunch of data all belonging to the same "thing". 2) They can perform a bunch of behaviours specific to that kind of "thing" (which may or may not modify the contained data). So, in the Class, each MouseRect object knows its own color, its own position, and how to draw itself at that position and in that color. This is called "encapsulation" - the idea that the outside code doesn't have to be cluttered with loads of trivial information about the objects, and doesn't need to correlate data from multiple sources - you create the objects with their own data (and
only their own data) already inside them, and then they carry it around with them (even if, say, you send them via Ruby links to another module).
If you use a Module, the memory use will be roughly the same, because the colours and positions still have to be stored persistently somewhere - e.g. in @instance_variables of each "hosting" RubyEdit. And whenever you want to draw a different rectangle, you first have to modify the Module by moving data from your external storage into the Module (of course, there is some data movement for instances of a Class, too - but the Ruby "engine" does this "under the hood" much more efficiently than written-out lines of code ever could). In fact, for this case, the Module is the worst case scenario - you're basically just using "draw" as a stand-alone "function", so you may as well define draw to take "color" and "position" as additional arguments, instead of indirectly passing them via instance variables and "initialize" (though the Class would still be preferable even to that).
Think of a user-guide for one of your plugins. You would naturally describe a complex GUI as having a "collection of mouse handles", "several envelope nodes", "a grid of cells", etc. - you wouldn't say to the end-user; "the GUI has a collection of active mouse areas denoted by rectangles drawn according to arrays of correlated position and colour data" (at least, I hope you wouldn't!). The idea of "object oriented" languages is that your code can "talk" that way too...
- Code: Select all
# NB: Assuming the previous example definitions of MouseRect.
# Example1: MouseRect is a Module (not "object oriented").
def init
@colors = [Color.new(0), Color.new(64), Color.new(128), Color.new(255)]
@positions = [[0,0,1,1], [1,1,1,1], [2,2,1,1], [3,3,1,1]]
end
def draw(view)
(0..3).each do |index|
MouseRect.initialize(@colors[index], @positions[index])
MouseRect.draw(view)
end
end
# Example2: MouseRect is a Class (let's talk about rectangles!)
def init
@rectangles = [
MouseRect.new(Color.new(0), [0 ,0, 1, 1]),
MouseRect.new(Color.new(64), [1, 1, 1, 1]),
MouseRect.new(Color.new(128), [2, 2, 1, 1]),
MouseRect.new(Color.new(255), [3, 3, 1, 1])
]
end
def draw(view)
@rectangles.each do |rectangle|
rectangle.draw(view)
end
end
Note that...
- Inside Example2's "init", we are explicitly making rectangles - all of the information about a single rectangle is kept close together and there's a single collection of data (the Array) with a clear purpose described by its name. As the list of items get longer, or the amount of data per object increases, doing it like Example1 with separate tables which have to be correlated, becomes ever harder to manage (i.e. much easier to accidentally introduce bugs into!)
- Example2's "draw" method is now responsible
only for drawing rectangles, and even if you're not so fluent in Ruby, the code pretty clearly says "draw each of these rectangles". OTOH, Example1 version is clumsy - it is doing a bunch of data correlation which has nothing to do with drawing, the multiple Array lookups are inefficient, and you need prior knowledge about the data in order to work out what it's for (very obvious knowledge in this case, but not every case is this simple!)
Object-oriented languages weren't invented in order to make code more efficient for the CPU or memory, and they rarely do. They were designed to make coders more productive and less prone to mistakes. The main idea is that the code better describes (to a human reader) the problem that is being solved - you are meant to use objects to hide the raw "nerd-speak" data and "functions", so that you can talk in terms of "objects" that are little models of the real-world things that they represent. It is possible to write Ruby code that behaves like a bunch of static functions, but doing it that way will always be an uphill struggle, because Ruby just isn't meant to work like that.