("Kandi Funky House" by Bobby Cole) - All right, welcome to my talk. If you haven't guessed by
now, it is on inheritance and composition in Ruby. So, a little background on this
talk, why did I write this? So, when I started looking
into these concepts, I found a lot of information
that was kind of geared around more experienced developers. Maybe it was developers
coming from another language that already knew these concepts, they just wanted to know
how to do them in Ruby, but I didn't find a ton of material that kind of combined
these two ideas in context so that you could compare
and contrast them. So, I wrote this talk for people like me, in that scenario, that maybe
you're new to programming, maybe you're new to Ruby, maybe you're new to object-oriented programming, maybe you've just done functional, and you wanna understand these things, and understand why people tell you things like prefer composition over inheritance, so let's get started. So, inheritance is a little
bit of a sensitive subject for a lot of programmers, especially when you talk
to seasoned programmers, it's kind of interesting, the
feedback you'll get about it, and I think that this is
mostly due to a lot of people getting burned by inheritance. I think a lot of people have
bad experiences with it, being implemented poorly or
not really fitting the problem that it was used to solve,
and I'd love to troll my Elixir friends by talking
about inheritance, so, that's where that comes from. So yeah, when you talk about inheritance, you get a lot of angry people. You know, they just
don't wanna deal with it, super mad about inheritance,
or maybe just sad, you know. So, the goal of this talk
is really to give you guys a firm understanding of
what object inheritance is and what object composition is, so that you can understand
the tradeoffs of both. I'm a firm believer that
there's not an absolute solution for problems that you're trying to solve, at least in programming, so
we really need to understand the problem space that
we're trying to work in and understand the tools
that we have available so that we can solve
that problem effectively, so that's what this talk
is hopefully gonna do, is give you that understanding
of these two tools. So, before we can talk about inheritance, we need to talk about
Ruby, and I'm just gonna go into some actual demo code. Can everybody read that okay? Bigger? Okay, so, this is about
as simple of an example as we can get, so puts Hello World, this is probably one of the
first things that you saw wrote in Ruby, but has
anyone ever thought, like, how does this actually work? Where does puts come from? How come I can use it in
a top level instantiation like this? I hadn't thought about it
until I wrote this talk, so maybe I'm alone there, but it's actually really interesting, so, we've got some
different examples here. If I run this file, obviously it works. It's gonna output Hello World! That's gonna work top
level, it's gonna work if we put it into a class method, so this one, we're just, we're
defining a new class, Hello2, we have a class method,
using the self keyword, so we're gonna call it on the
class itself, called greet, and then we're gonna put
just a message in there. You can see that when we run this, we get Hello from a class method,
just like we would expect. So, when we start to get
into the internals of Ruby, what we're gonna start,
we're gonna find out, is, when we start to have
these naming collisions, you get some interesting behavior, so, what we're talking about here, oops, is method lookup, so, when we get into something like this where we have a class and
this class actually defines a puts method, and we're
just accepting a message, what's gonna happen is when
we call puts on line 10, it's not gonna use the normal
puts that it usually does, it's actually going to use
the puts method on line four, so we can see this working. Instead of actually outputting hello from an instance method, you
can see in our puts method, we're just ignoring that message, and we output, What is the password? So this is kind of the
very basics of inheritance for a method lookup, and I'm not gonna go into full method lookup, that's
not the point of this talk, but I really just wanna give you an idea of how this stuff works,
so, what happens is, in a Ruby file, in a class, when you call a method like puts, it's gonna check to see if that class has that implementation. If it does, it will use
that implementation. If it doesn't, it will
continue to look for it, and we'll explain where
it goes to find those. So here in our next method, we've got another interesting example. We'll skip over the speaker for now, but right down here,
we've got a class Hello4, and this little less
than sign is telling us that we're gonna inherit
from this speak class, so we have a speak method, we call puts, and you'll see from the output that we get Hello from the great unknown! So, it's stopping here, and we're putting, this is a global variable,
standard outputs, and that's how the actual puts method works under the covers. Does anybody know why we
can't call just puts here? - It'll call itself.
- It'll call itself, perfect. So, if we were to instead put, well, get rid of that. So we get a stack level too deep error, and you can see this, we get from Hello4, and it just keeps looping over puts, so it keeps calling itself recursively. That's not what we wanna do. In our next example,
so we have Hello5 here. This one is going to, it
has an attribute on it, and it's gonna initialize that attribute with just a string and its value, so it also has another method called puts, and you'll notice my linter
down here is actually telling us a little error, it's telling
that we have a duplicate method so, this is kind of interesting because Ruby doesn't necessarily respond to messages with only methods. It can also respond with attributes, and that's why you see this here, so we're defining kind of a
responder on puts on line four, and again, on line 11, so,
when we call puts here, it's kind of a mystery, like,
what we're gonna get back. Are we gonna get back the method, or are we gonna get back the attribute? So the method is set right
now to accept a default value, or it doesn't need a value,
it'll default to nil, so when we call this, we
get What is the password? We don't actually get
the string attribute, and why that is, is in Ruby, the kind of the last one wins, so if we move our attr_reader below this, you'll notice that my
linter changes, right? So now it's telling me that
this is the duplicate method, and you can imagine that when we run this, it's not actually gonna output anything because it's just returning a string. If we change that to actually call puts, we'll get the string attribute. Okay, so, on this example,
we have another class with another method that
you're used to seeing now, but now inside of speak, we
have puts as a local variable. When we call this, we're
gonna get that same thing. We're not gonna get a return value, unless we actually output it, oops. And then we get the local variable, so kind of the basics
of this method lookup, is going to be, check inside of my method that I'm calling this from,
if I have a local variable, cool, use that, if I
don't, go into my class, do I have a method? Use that, if I don't,
go to my parent class. If I have a method, use that,
so, to answer the question, where does this puts come from, we've got our last example here, and I get undefined method puts, so you might notice something
kind of shady about this file requiring the setup file,
so I'm sure that's where a lot of you are drawing your attention, so if we look at this setup, you can see that module kernel, we have removed the method puts. That's why we get that error. So, in Ruby, we'll go over
this in the next few slides, every object that you create
will have an inheritance tree. That's just how Ruby is built,
and in that inheritance tree is a kernel, and that's where methods that you probably take for granted or never thought about,
like puts and sleep, are defined, so any time you call those, you're bubbling up that ancestor tree until you get to that kernel class, unless you've actually defined them. All right. So, now that you know a little
bit about the method lookup, we can start to talk about inheritance, so what is inheritance? So this is from Wikipedia. I won't read it verbatim,
but the basic idea is that, you have a child object,
and when you inherit, that child object will
acquire all the properties and behaviors of the parent
that it inherits from. So you might have heard it described that inheritance should be
used for an is a relationship, so an example, a duck is a bird, where a duck would inherit from bird, so you could set up a base class of Bird and give it common attributes
that a bird would have, like wings, feet, whatever you want, and then you could make
a child class of Duck that would inherit all of those features and then specialize it for
stuff that only ducks have, right, like a buoyancy level
or something like that. So inheritance in Ruby, we
have a couple different types and tools available to
use it, so the first one that we talked about and one
that you've probably seen all over the place, especially
if you're a Rails developer, is this little less than sign,
so, in action controller, active model, stuff like that, you're gonna see a lot of inheritance in those framework-y stuff. So, this method down here,
so I've got at the end, Child.ancestors, is this
method, this ancestor tree that I was talking about,
so, with nothing else, we have a child and its
next ancestor is a parent. A parent's ancestor is an object, and then it goes to kernel,
and then to basic object, so any class that you create outside of this parent inheritance
will have these kind of, it inherits from object,
it inherits from kernel, it'll inherit from basic object. We also have multiple inheritance in Ruby, so, there's more than one way to do this. We're only gonna talk about include. The other two, there's
also extend and prepend, but they're kind of outside of this talk, so, we have the option to
include multiple parents, so you can see that we have
in our ancestor tree a child, and the first thing that comes after child is the last thing to find, so
in this case, it's StepParent. If you were to switch
those two, parent would, you'd actually look in parent
first, and then step parent. And then, again, we've got
object, kernel, and basic object. So, multiple inheritance, any dangers that you've heard about single inheritance is kind of just exacerbated
in multiple inheritance. There's a lot more moving
pieces and a lot more chances for you to miss something. And then the last thing I
wanna touch on is namespacing, so a lot of people will
see, include a module, and they'll kind of think,
oh, well I've seen classes wrapped in modules, is that inheritance? That's actually not,
this is, it's literally just kind of qualifying
and creating a namespace around your class, so
you'll never refer to Child without that Parent colon colon. You'll always have to access it that way, so it's literally just
giving it a different name, so that you can group things together. All right, so, when should
you use inheritance? And the answer is, when you
wanna specialize an object. A child should use every
single attribute and method of a parent, if you really
wanna use it correctly, and you should never
use inheritance as a way to kind of clean or dry code up. There's much better, much
better ways for doing that, we'll actually talk about
one here in a second. So, the pros of inheritance are, you can make code reusable and extendable, but there's a pretty big caveat on this, and that is, if it's done correctly. If you don't use inheritance correctly, neither of these things are true. It actually makes it
really hard to reuse code and really hard to extend code. The cons, when you inherit
from a parent class, you are literally creating
a coupling between the two. That's usually a word that's
synonymous with bad behavior. You get reduced readability, so, if you have a method in a parent class that's not in a child,
but the child calls that, a developer that's looking at this fresh that's never seen it before,
is obviously gonna have to do some digging to figure out
where that method comes from. It's easy to abuse, so, I would, I would argue that
inheritance and its benefits are really tied to properly naming things, so you can create these
taxonomies or these hierarchies in your code, where you can
create kind of a visualization of how your application works. You've probably heard before
that one of the hardest things about programming is naming things, so I would argue that a
framework around naming things is probably gonna be hard, too. I think that's why a lot
of people get it wrong, is it's hard to define those
domains in our systems. And the last point that I wanna bring up is that it can complicate unit testing, so if you're inheriting, it's pretty hard to isolate a child and
only test child behavior, because you get all of that
parent's behavior for free, and I'll have an example to show that. So, you might be feeling
like Tina here, a little bit, like, there's only one pro
and there's a bunch of cons, so, why are we even talking about it? Should you just never use it? I think there is a use case for it, but, we'll see how limited that can be. Okay, so, next I wanna talk to you about the alternative to
inheritance, composition. So, what is composition? So again from Wikipedia,
it is a way to combine simple objects into more complex objects. So you might have heard it described as a has a relationship, for instance, a car has an engine, where a car would be composed with an engine. So composition in Ruby. We have, this isn't really
talked about in Ruby, but it's worth bringing
up so you know about it, so there's functional
composition, which is really just creating a method from other methods, so again, you're not
gonna see this in Ruby, but if you look at Javascript examples, or examples in other languages,
you'll see this quite a bit, and then we have object composition, so, this example is
okay, but I've actually, I've got a different one
that we'd wanna show, so we go back here, and I don't have it. Okay, so, I kind of wanna just explain what composition is and how you get to it with a concrete example
that you can actually see, so, we're gonna, we have
a very simple application for explanation purposes, and
this is gonna have one method where we're gonna call an API, and we're gonna save the result, so, there's a couple of problems with this type of programming. Right now, the structure
of this is very rigid. It'd be very hard for us to come in here and actually change
anything with confidence, knowing that we're not
gonna break something. Now, this is very small
and easy to understand, but, you know, imagine this
in a much larger ecosystem. So, what are some things we
can do to make this better? So the first thing is, we can start to break out these dependencies, right? So instead of having this method know what its dependencies
are, we can move them out of that method and just
allow the method to access them, so this is kind of step
one in composition. Step two is gonna be,
we actually wanna inject those dependencies into this class, so we don't want the class
to hold the knowledge of what its dependencies are. We want the class to only receive that and know that it can do things with them, so, this might be new to some people, it's just a key value argument, and these are just default values, so we're passing in a DB,
which is this Repo class, and an API, which is that API class. We're setting those to attributes, and then we're using them in a method, so the benefit here is,
we're now able to update and change our application,
without changing the code, the internal details of this method, so, as long as we know that
if we create a new API object, all we need to know is
that that API object needs to respond to this get method, so we're able to interchange
anything that we want so long as it responds to this method, which gives us a lot of flexibility. Same thing with the repo, and you can do this with
anything that you see possibly changing in your application. Blake likes that.
(scattered tittering) Okay, so, when do we
wanna use composition? As much as we possibly can. Composition kind of pushes you towards making smaller
classes and methods. That's a great thing, that's
literally never a bad thing, so, whenever you can use composition, unless you know it's like prototype code or you're gonna throw it away, but it gives you a lot of flexibility and there's not that much work, so pros that we have, it's
flexible, like you've seen, you can interchange things
and swap things very easily, you can update your app pretty painlessly. It's readable, you're not guessing where things are coming
from, it's very explicit, it's all in front of you. It's easy to unit test. It's really easy to isolate
each part of the system that you're passing in,
and test it in isolation, which is what you wanna do in a unit test, and all the cool kids
are talking about it. So the cons, you have to
write a little bit more code, that's a con. Okay, so, we're gonna
dig a little bit deeper and start to look at some
examples of this stuff. Okay, so the first example
that I wanna show you guys, and this is to kind of
contrast, or to talk about why inheritance
can complicate testing, so I have a pretty simple user class here. We have an after_create method where we're going to just
populate an attribute which is a GUID. Other than that, we've
got some trivial methods like asking if there's a
first name, a last name, and a method to change the first name, so we get a new feature
request that comes in, that we need some sort of
administrator in our system. We don't wanna necessarily
write all of this logic. We wanna use what we've got for user, but we do wanna do some things
a little bit differently, so, we decide to make an Admin class that inherits from User,
and this class, right now, only has one extra bit of functionality, which is to populate this admin true flag. So, let's talk about testing, so when we test our user class,
or unit test our user class, it's pretty simple to
see, we're just gonna test all of our methods that we have available. Pretty simple, pretty straightforward. When we go to look at our admin class, we're gonna test its method, so there's a huge problem with this. When we inherited from
the parent class of User we inherited all of those methods, so, how this can complicate testing, is if we don't test all of
those parent methods in Admin, somebody can go in and change our user, and not know that we're
dependent on those methods in Admin, right? So someone can go in, they
can look at User class, and say, hey, this method's really dumb. I don't think we should use it anymore. I'm gonna delete it. They delete it, they run the test suite, their user test breaks, so they go in, they say, of course it
broke, I just deleted it, they fix the test, deploy
the code, everything's good. Later on, you figure out
that that Admin class is actually using that method, but you don't have a test for it now because it was tied into your user, so, in order to really be
a responsible developer, you're gonna have to
duplicate all of the tests in your parent object, in your child, to know that you're
actually covered, right? If you were to do that, you
would have another failing test in Admin where the original changer probably could have seen, oh, this is used in a little more places. I need to actually go
do some investigation, so it's easy to overlook those
little details like that. I've got another one. So, flexibility, so we're gonna check out another example. Oh. Okay, so, we're gonna
talk about how composition can help you when you're
trying to actually isolate and test, so this is kind of
the contrast to that problem. So, we have a pretty
simple SignUp class here. It's got a couple attributes. The ones that are interesting are that persisted and invoice. User payment processor and user repo are just for our initializer so that we can pass things into it, and that's the boilerplate
that I was talking about, so you're gonna have to
set these classes up, if you're gonna use composition, but it's really not that much more work. We have a branch in here,
so if we can create a user, we're gonna set persisted to true, and then we're gonna set invoice to true, if that payment processor
can send an invoice, and if it can't, we're gonna
set both of those to false, so, it's a pretty contrived example, but what we can do when
we're testing this, is we can test each one
of our logic branches however we want, right,
so we know that as long as we pass in a, when we're testing this, as long as we pass in a user repo, that responds true when you
try to call the create method, you can get to this true logic, right? So you can stub out that
actual saving and persisting because that's not really what
you're trying to test here, right, you should have separate
tests for your database actually making sure
that you can save things, and then same thing with
the payment processor, so in this test, we
shouldn't really be testing if the payment processor
can send an invoice. We just wanna test that when they can, we actually mark it as invoiced, so you're able to
isolate the bits of logic that you actually care
about in this class, and allow your other
classes to test the logic that they should be concerned with, so you can pass in, you know, you could create a new mock
class, actually I think, yeah. So, this is a simple example where instead of passing in
the real payment processor, or the real repo, you
could create new classes that hold that, and that mock processor could have a create method
that just returns true. You could also create an open struct, so as long as you create
something and pass it and it can respond to that
message, and you can change that, you can isolate your
tests a little bit better. All right, so, the funny thing is,
when I wrote this talk, I was really trying to defend inheritance. That was kind of my main goal. I know it's in the Ruby
language for a reason. I know that there's some
good in inheritance, and I really wanted to find it. It was pretty difficult,
there's a lot of times where it just, it
doesn't solve the problem that you think you're
trying to solve with it. But I did think of one that hopefully is a little bit helpful. So, if you have an API where you have common things
that you're always gonna share across all of your API calls, like an API token, a key, you know, any type of, kind of setup stuff that you have to do
with your API provider, I think that inheritance
is a great tool for that. What you're able to do is
you're able to actually have these headers defined in one place. If you have very similar error handling for an entire API in different routes, you can put all of that
error handling in one place, and then, sometimes you
get data back from APIs that isn't exactly what
you want it to look like, and if it's consistent,
you can also kind of clean that API response in one place, so what you're able to do, is create more specialized versions of this class, so this one is a product class. It inherits from that base object, so I can just call get with
the route that it needs and then I don't have to worry about any of my authorization or
credentials or anything like that in this class, and then if I
need to do something special to clean it, like maybe
the index on this one API returns in just a really weird way, I can specialize that call and clean it up the way that I need to, without all of the implementation details. All right, so, if you're
a seasoned developer, you might be thinking, what about STI, which is single table inheritance? I didn't write this talk for
single table inheritance, but I'll just kind of
give the same warning is, it's basically inheritance
taken to a database level, so you get all of the same
issues that you can get with class inheritance, and
I haven't had to do this personally, but I've heard
that splitting them apart after the fact is a lot harder, so just make sure that you
really have a good use case for it before you
consider it for an option. That's all for my talk. This is my contact information. That's my dog, her name is actually Ruby, 'cause I love Ruby so much.
(audience cooing) She's very dorky, but yeah,
if you have any questions, that's my Twitter handle,
or send me an email, or come up and talk after. I'd love to hear any
questions you've got offline. I work for a company called Nav. We're in Salt Lake, really cool company. We're growing a ton, so, and
even considering remote work, so if you wanna work
remoter, come check us out. If you have any questions,
come talk to me. Cool, thank you.
(audience clapping) (whooshing)