Luke Smith: Class Inheritance and Composition Patterns in YUI

Uploaded by yuilibrary on 11.11.2011

>> LUKE SMITH: If any of you have given presentations before, you know that having twice as many
slides as you have minutes is a bad idea. Yeah, this is going to be fun.
All right. My name is Luke Smith, I'm one of the core developers on the YUI team, and
we're going to be talking about inheritance patterns. We're running a little bit late
so it's going to go fast. Hopefully I won't be speaking too quickly to melt any brains.
Let's go.
By way of introduction, here we are again: browser wars. It's a lot of old news again.
It's different now than it was before. We have new browsers that are better. Old browsers
in the old days, they sucked, the hardware was terrible. Now we have more devices and
it's generally hard to be a developer; that's not new. But the context of what makes it
hard today is definitely different than it used to be.
But one thing that hasn't changed from the old days to now, and even the time in between
that, is time. It continues to be rare that we are given enough time as developers to
build out our applications in such a way that we are meeting all of the requirements of
accessibility, internationalization, and really future proofing the code that we're writing
so that it ends up not costing us time over the life of the application in the maintenance
cycle. Time does break down into that basic division here between the time spent upfront
building the thing, and then the time spent over the life of the application that you're
syncing into feature enhancements, bug fixing, and my God, I can't believe I wrote that code.
The development time is really where you put in the investment to reduce the maintenance
time. Development time breaks down into two basic sweeping generalized categories of prototyping
and structuring. Prototyping is where you just get the thing working, right? If you've
done any hack days, that's all it is. But you are hating your future you if you stop
with prototyping and push to production. You have to put your code into some sort of structure,
creating those relationships between the objects in your system, properly modeling the objects
in your system, and organizing them in such a way that over the life of the application
if something comes up, you know where to look. Or things are isolated well enough and combined
in such a way that it's trivial to update and maintain over the life of the application.
So structuring is where the investment really happens.
That's obviously what this talk is about, so let's get into it. YUI comes with a lot
of stuff for creating infrastructure out of the box and getting that rough framework for
you to put your application in. We have the CSS, we have the modules, and loading, and
the custom events for doing more decoupled relationships between objects. But we have
to build classes on top of those things and work with other objects on top of those things,
and there are a lot of different ways that we can create the relationships between those
objects; the types of patterns that we can fit those relationships into.
I have a lot of different strategies to cover, and I'm going to start with the two native
styles of inheritance relationships and then go on to the artificial relationships that
we build into the library. We have methods for making it more convenient to use the native
inheritance styles, and then when we start getting into the artificial structures, that's
when, in my opinion, you get a lot of payoff for your maintenance going into the future.
Starting with native, pseudo-classical. This is old school, everybody's done this, right?
I mean, there can't be anybody in the room that hasn't written this back in the day,
creating a superclass to subclass relationship by just assigning the prototype of the subclass
with an instance of the superclass. If you've done this before, you should probably know
why this is bad. But we've had the extend method in the library since the first versions
of YUI2. You see this pattern used in libraries everywhere, and outside of libraries. There
are a lot of versions of an extend method, and this one is ours.
If you just take this one line and replace it with Y.extend, it's already better. We'll
go over why it's already better in just a second. The extend method adds that extra
sugar of saying OK, we know that you're planning on creating a relationship between one class
and another class so we'll add in some additional arguments to allow you to define the class
as well. The third parameter to extend, for example, is where you can add in your prototype
properties. There's a subtle difference between doing this and doing individual prototype
assignment or just saying subclass.prototype equals some object literal.
Then a lot of people don't actually know that there is a fourth argument to extend, which
takes the static properties that you want to decorate your subclass as static methods
or static properties. This actually comes in handy largely when we start extending the
infrastructure pieces in the library, like Base, or widget.
But then there's one other little trick with extend, which is that it returns the subclass
that you pass in. You don't have to define the subclass first and then call extend, you
can actually use extend as a class definition method if you want. You're just passing in
the constructor function as the first argument to extend, and it'll return that. Then you
capture the return value of extend and boom, you have your subclass with your constructor
with your extension relationship, your instance members, your prototype, and your static properties.
We're going through extend kind of quickly because everybody's used basic extend. It
is the meat and potatoes of class relationships, subclass-superclass relationships in JavaScript.
It is using prototypes, so it preserves instanceof, and it's very bare bones, and it's fast. Extend
also adds that static superclass property so that you have access to overridden methods
if you need. We'll talk about why the control of the superclass constructor execution is
actually a benefit when we start talking about some of the other techniques.
The cons for extend are really relative to the other methods of creating relationships.
For example, there's no multiple inheritance because inheritance goes through the prototype
chain and the prototype chain is linear; it does not fork. The constructor chaining is...
while you have control of when and where it happens, it is kind of awkward to do. It tends
to look like this, and I'm sure everyone here has memorized this and typed this out. I mean,
you can prattle it off by memory. No, because it's kind of awkward, right? It's really long
and idiomatic.
And if the superclass that you're creating a subclass of doesn't actually represent a
set of functionality that's going to be used heavily by the subclass, or by the instances
- if the instances aren't going to be called with the superclass methods directly - then
maybe the subclass superclass relationship, isn't the right relationship for that. You
might actually be incurring a cost for calling the superclass constructor if you're not going
to be using those methods. We'll talk about different methods to avoid constructor chaining
in the superclass, or establishing different relationships for your subclass-superclass
and other types of features.
To sum up, it's good for the basics. But if you can use Y.Base - and I will mention this
again - use Y.Base, because there are a lot more options for you. It brings so much more
to the table in terms of not only feature set for the classes that you have but also
for the structure that you can establish on top of those classes, or to even assemble
those classes.
Now let's take a side jump through prototypal inheritance. I have probably more slides in
here than I should. This is a bit of an aside and we're going to take this aside by a route
through the extend method itself.
From time to time we get questions about why YUI doesn't use prototypal inheritance and
instead uses pseudo-classical inheritance, and the answer is, well, we kind of do, but
for most modern web applications, pseudo-classical inheritance makes more sense, and I'll actually
show you an example of why. Inside of extend, here you see we're calling the Y.Object method,
and the Y.Object method is the method for establishing a prototype relationship. Doing
prototypal inheritance, it all goes through Y.Object; you don't have a lot of options
there. That's where it comes from.
This is it. That's the method. If you are on a browser that is ES5 compliant, it has
the Object.create method so it's even smaller than that actually - it just calls object.create.
But in essence, it does just this. It actually is less complicated than what it looks like
here. There's a little magic with the anonymous function wrapper and the private function
F there. You can think about the Y.Object method being just these two lines here, where
you're setting the prototype and then you're returning a new instance of F.
How does that work in comparison with the new superclass method of old? If we had a
superclass that we wanted to assign into the prototype slot for the subclass, calling the
constructor is bad. We just want to establish an inheritance relationship between these
two classes - we don't need an instance to represent anything, we just want to have all
of the methods on the prototype of my subclass and have that instanceof relationship established
between those two classes. Calling the constructor is actually a cost. Also, you might have to
pass something to the constructor in order for it to do something. It might just break
if you don't pass arguments to the superclass constructor. But what are you going to put?
I mean, you're just trying to establish your relationship.
What Y.Object does in this, if we walk through it, is since the constructor is what's creating
that cost, we avoid it by taking the prototype object and copying it over into this empty
function here and then creating an instance of that. Since the constructor for this is
empty, it doesn't do anything, there's no cost. But because this prototype here was
the same object as that prototype here, when we create an instance of this we do maintain
that instanceof relationship. But there's still a cost here, and that is that the superclass
constructor is not called when you create an instanceof subclass. It's not called automatically,
ergo the need to manually call the superclass constructor to chain those constructors, and
that big long awkward thingamajig.
But if you want to just do pure prototypal inheritance, then just using Y.Object you
don't have to be working with a prototype of some other class, you can just be working
with any old object. You pass it into the prototype there and boom, you create an instance
that has all of the properties of that object through its prototype relationship. You don't
have to copy over individual properties, you just get them for free. Plus if you modify
that object then those show up for everything that inherits through the prototypal inheritance
for all instances that are begat from Y.Object.
But in practice it doesn't have an awful lot of use because the fact that the properties
of this object aren't own properties of the instance, they live on the prototype, means
that you can't enumerate over them for example. If you want to do an enumeration with hasOwnProperty,
all of those properties on the prototype are going to return false. That actually can get
in the way in terms of common patterns of code use where you're just going to be iterating
over the properties of an object.
One pattern that is useful for using Y.Object and the prototypal relationship is the factory
constructor pattern. If you follow along what's going on here, I'm defining a class set and
instead of working with the "this" object I'm going to be working with the "that" object.
I assign the "that" object dynamically based on what this is inside of the function.
I'll show you an example of why this matters. If I call a new set, then inside of my constructor
the "this" object is going to be an instanceof Set. So instanceof Set is true, right? But
oops, I've forgotten new. That means that the "this" object inside of the Set constructor
is going to be the global object. We really don't want to be modifying the global object
in our constructor. Using Y.Object, we'll do what we did for the extend relationship
-- do you remember the extend relationship? We took a Y.Object of the prototype of the
superclass and basically what that does is it creates an uninitialized instance of that
superclass. In this case, we're going to create an uninitialized instance of the very class
that we're inside of. Incidentally, we're in the constructor so we're going to then
continue on with initializing it. As long as we remember to return the "that" object
instead of the "this" object then we're all good. What comes out the other side will also
be an instanceof Set.
That's one way that you can cover your butt a little bit, or give your users the opportunity
to fail but not have it result in a code failure, using the factory constructor. The YUI class
itself actually does this as well, so that's why you say "YUI()" -- that's effectively
saying "new YUI()".
OK. Enough time in prototypal inheritance. The relative pros and cons are... Like I said,
it avoids copying properties. Because you have all of these properties hanging out in
the prototype, that means if you assign a property to that object it can shadow one
of those properties in the prototype. But if you then delete the property, it hasn't
deleted it from the prototype. You can create a sort of "here's my original values, I'm
going to create an instance derived from that and then I'm going to update it, but then
I can revert to the original values by simply deleting those properties and letting the
prototype properties shine through."
You can use the factory constructors if you want. In practice, the avoiding class explosion...
I haven't seen it be a perfect fit for that, myself.
The cons are a short list but they are fairly significant. You don't get the multiple inheritance
which is because it's going through the prototype chain. The factory constructor is nice, but
it is effectively dead code if people are using new like they should be. You're adding
code to protect against the improper use of your code. Then the hasOwnProperty gets in
the way really quick, let me tell you. I've done some prototypal inheritance based code,
and the fact that I can't do a hasOwnProperty any more, it just feels sort of exposed and
uncomfortable. It's so easy for me to just automatically type that hasOwnProperty in
there that I'll add a bug by just mechanical typing.
It doesn't really fit in with a lot of the problems in web development today. What I've
found, at least in patterns that I've seen for use of prototypal inheritance, is that
this tends to emerge. We'll take this prototype object and we'll beget a few instances of
it, but whenever we beget instances of it we're always going to be updating those instances
to differentiate them from the prototype itself. But when you're differentiating it from the
prototype itself, you're usually doing it in the same way, which is a constructor.
Here we are again, back in the pseudo-classical inheritance pattern. Now let's move on.
That was the native methods. Now let's get into the fun stuff, which is when we start
creating artificial relationships between stuff that will throw instanceof under the
bus. instanceof... I don't care about instanceof.
The first is going to be augmentation. Augmentation is the first approach that I'm talking about
now for creating a multiple inheritance relationship. The key API in question is Y.augment. You
can see Y.augment being used in a few places in the library if you course through the code,
which I recommend, incidentally. In particular, ModelList is an example here, where we have
the ModelList being defined by its constructor. You can see that it's actually extending Base.
Its extension hierarchy is already taken, so we can't extend arrayList also. We have
already extended Base. Instead what we'll do is we'll augment arrayList.
What this does is it takes the ModelList class as it exists right now, as a Base subclass...
You can see here, we have a constructor for ModelList which is chaining into the constructor
for Base, we have some Base methods and we have some ModelList methods in the prototype.
Then what the augment does is instead of just mixing the methods directly under the prototype
and then creating some relationship to the constructor -- I wouldn't know how you could
do that really easily anyway in pure JavaScript - instead what we're doing is we're defining
the sort of placeholders of these activation functions on the prototype, which map to the
names of the APIs on the prototype of the augmenting class. When we create an instance
of ModelList we're only calling the constructor for ModelList, which then chains to Base,
and boom, we have an instance. The instance inherits the prototype, which has these activation
Now, if we call each on this instance, it's going to then fall over to the prototype's
implementation of each, which is then going to send this activation signal through the
augment infrastructure to do three things. The first thing it does is it copies over
the individual methods from the arrayList prototype directly onto the instance. Then
it calls the constructor, so now we have the instance here having gone through the appropriate
set up to make it work as an arrayList. It has the methods and it has gone through initialization.
Then finally we call that method again on the instance, which now is the version of
the method from the arrayList prototype.
It's kind of complicated and convoluted, and it only gives you limited control of... Here,
let's go through this fancy animation one more time. When we call the constructor here,
how did we know what we were calling it with? Because we created an instance of ModelList
earlier and we were passing arguments into that constructor, but the arrayList contructor
was called automatically for us so we didn't get input into what we could pass into that
Augment does support limited configuration into what you can pass in with the fifth argument.
I'm not actually going to talk about the third and fourth, because in practice they're not
very useful, but the last argument to augment is the argument that you would pass in to
the constructor of your augmenting class. It's good that you have some control into
what goes into that constructor - it's less than excellent that you only get to define
it once. There's no instance level control. That goes into the pros and cons.
If you're with me here, the augment interaction and setting up the relationship between one
class and the augmenting class is kind of complicated. It's a little intricate. The
challenging part, really, is establishing a relationship of a class that already has
some extensions, or that already extends from a superclass. How and when do you trigger
the superclass constructor?
What augment aims to do is it targets a situation where the class that you're adding the behavior
onto your class with... You have multiple inheritance, so these class behaviors are
maybe less useful and maybe aren't going to be called very much, but in going through
the constructor there's going to be some set up costs for them, so you don't want to incur
that cost on all of your instances unless you have some signal from the implementation
that they are going to be used. It defers the constructor of the augmenting class. It's
a special kind of lazy multiple inheritance, basically.
Because it is multiple inheritance, that is a pro. Now we're able to do multiple inheritance.
You give up instanceof and you've given up the static link to the superclass, because
now we're relating to multiple classes.
This is also low level, like Y.extend is low level. You can just do this with any function
that you want to. Augment handles this at a lower level.
The cons for augment are also perhaps pretty obvious, actually. The first call to one of
those augmenting methods is costly. The call to the constructor is basically a wash. You're
either going to do that in your subclass constructor, chained to the augmenting classes constructor,
or when it gets triggered by that augmented method to call the constructor and then call
again to that method, that's basically a wash for doing the call to the constructor. The
cost is that we are creating copies of the functions... We're copying all of the individual
functions from that augmenting class onto each instance, and that's going to consume
memory and it consumes time. You have to ask yourself really if the value of the deferred
constructor is worth the potential cost in memory and the initial hit that you'll take
with that first call to one of the augmenting methods.
Also, because we get into multiple inheritances we have to worry about the diamond problem,
where you have subclasses inherit from multiple superclasses, and those superclasses inherit
from a common superclass themselves. You end up going through a superclass hierarchy and
then duplicating some grandfather constructor logic which often messes things up.
OK. To sum up, you can use augment or you can just use this sort of pattern here, which
doesn't defer the superclass constructor but just inlines it into the constructor of the
subclass, just like it does with the superclass. For the prototype that you define for your
subclass, you just create your prototype as it is and then mix in the prototype of the
additional augmenting class. In this case, the prototype contains direct links to the
prototype methods automatically; there's no copying of instance methods or copying of
class methods over to instances. Everything exists in the prototype. You are taking the
hit in the constructor for the other class in your subclass constructor. If that's fine,
then this will likely end up being a faster multiple inheritance strategy as opposed to
using augment. Use augment if deferral of that constructor is really important.
Now let's get into two of the really fun things, which are plugins and class extensions. Now
we start to get into really flexible structures here. Plugins are... I guess I'm forecasting
a bit, but plugins you can think about being instance based, class extensions you can think
about being class based. The operative method in plugins is the plug method, and the plug
method is something that is on the host. The plugins themselves have very, very few requirements,
and we'll talk about that in a second.
If we walk through what happens here, we have the overlay class which is a Base-derived
class, which means it has plug. In order to plug something in, the class that you're plugging
it into needs to either extend or be augmented with, or just have, the APIs for Y.Plugin.Host.
Base gets that for free. Nodes also get that for free too, so you can plug in Nodes, which
is pretty awesome. We'll create an instance of overlay, so now we have these attributes
on this instance of overlay here, and now we introduce this plugin drag class, which
exists in the library. When you call plug, what it does is it calls the constructor and
creates an instance where it's passing in the overlay instance as part of the configuration
passed into the constructor. But really this is just an instance of a class.
We basically have two objects at this point, two objects that each have their own respective
set of attributes, that each have their own respective prototypes. It just happens to
be that the instance of this plugin is coded in such a way that it takes that host configuration
and it wires itself into that host to listen to its behaviors, to listen to its events,
and maybe add some advice through AOP methods, Y.Do.before, Do.after, and some other sugar
methods that actually are available on Y.Plugin.Base. Then it takes the namespace that is configured
on that plugin and it just assigns a new property on the instance of overlay here, called dd,
that points directly to this instance. Basically, at the end of the day, we've created two objects
and then we've assigned one object to the property of another.
That means that overlay.dd is a separate object that just knows about overlay and it works
with overlay. It's kind of wired into it. But it does maintain its own stuff. If you
need to set one of its properties then instead of setting a property on overlay, you're going
to be setting a property on that other instance, the drag plugin instance. I love the flames.
When you unplug, though, the relationships and any wiring that went into that class goes
away, and that's part of the contract for plugins that we'll talk about in just a second.
The requirements for plugins are that. That's it. The host has to have the functionality
of Y.Plugin.Host, as I mentioned, and all the plugin class needs is a static NS property.
It can do anything it wants; as long as there's a static NS property on that function, it'll
work as a plugin.
[audience member raises hand offscreen]
Ask later, man.
>> LUKE SMITH: The plug method, as you can see here on PluginHost, basically boils down
to this conditional here: if the plugin is there and it has an NS, then create a new
instance of that plugin and assign it to that namespace. Everything that happens inside
of that constructor, it's up to you. At the end of the day, we end up with the dd instance
here, or the drag plugin instance here with its own set of APIs. Then you can unplug it.
You can actually unplug it by namespace as well.
The contract is where we start to get into the relative pros and cons of plugins, in
my opinion. That is, plugins should expect to be working with the host object, right?
Otherwise they're just sort of vestigial functionality that doesn't care about where it's hosted.
It being a plugin, it should really care about its host. That's not too much to ask, I would
They can provide their own APIs. They could just be a plugin that you add to a host and
it just modifies the behavior of the host, it doesn't add its own API at all. But you
can provide your own API. You can either provide your own stuff or you can modify the host.
There are two things that it must do and it must not do. It has to remove all traces when
it's unplugged, so you can't permanently alter the host object. You have to make sure that
everything you do to the host can be undone cleanly. You can't modify the host directly
- you have to provide your API on your own instance, on the plugin instance itself, which
means that that API is namespaced. So don't modify the host object, just listen to its
events, listen to its methods using AOP advice methods.
Some people don't know that you can actually plug a class. What this does is it makes it
so that when you create an instance of overlay, it is automatically then plugged in with the drag plugin, because you've plugged
in the class with plugin.drag. You can think about this actually as similar to the DOM.
You have an element having a style property, which has its own API. It has its own properties
and that is not duplicated on the element itself. This is auxiliary functionality that
is related to this element, but it is not part of the core behavior of that element.
This is additional functionality, and that's the distinction for going inheritance versus
going plugins or extensions. Well, mostly going plugins. You have core behavior established
through the prototype and through extends, and then you have additional behavior through
Let's get into the pros and cons. If you start mixing in a bunch of classes into the same
host class or the same subclass then you can run into naming conflicts pretty quickly,
especially if you're going to be using common verbs for taking actions. So having things
namespaced helps prevent that naming collision.
The ability to plug classes or instances is pretty nice because it adds a high degree
of flexibility. The very few number of requirements that it takes to be a plugin adds a lot of
flexibility. The fact that you can work with Nodes adds a tremendous amount of flexibility,
because you can use Node plugins to be like mini widgets. Or if you don't need to do a
lot of complex state management, you just want to enhance a Node, then you can just
write a Node plugin that does all of that. Maybe over time you might want to evolve that
into a widget, but even if you do that, you can evolve that code into a widget and then
keep a Node plugin that just creates an instance of that widget plugged into that Node. Node
plugins afford you the ability to code from an instance related to a Node, or from a Node
that is augmented with this behavior. It allows you to code in either direction, whichever
your preference.
The cons for using plugins. Cons are always relative, right? It fragments the API, but
maybe you think that this function is kind of core, or it's just kind of a nuisance to
be having to set attributes on a namespace thing. It's just confusing to... I just want
to set the attribute on my overlay, I don't want to set the attribute on some property
of my overlay. It ends up looking odd in the code, or maybe that just feels awkward to
you. Maybe it actually feels right. Totally a stylistic choice. But there is an opportunity
for confusion there for future developers that are then maintaining your code. That's
why I listed it in cons, but again, it's relative.
For the plugin contract to be non-invasive on the host, that can be a little costly.
It also means that if you have multiple plugins then they can start stepping on each other's
toes, and you have to balance out which plugin comes first. If I have a plugin that actually
requires another plugin to be on there as well then those relationships can start getting
a little complicated.
Finally, plug. This is a temporary problem. I think we're going to make plug a little
sugary, or a little easier to use than passing in the class that you intend to plug in.
So yeah, plugins are terribly flexible. They tend to be better in small numbers, at least
in my opinion. When you ask yourself if you want to use plugins or augmentation or multiple
inheritance via mixing into the prototype and chaining all of the superclass constructors,
it becomes a question of do you want to use augment to defer the constructor? Would the
behavior be more likely to be expected directly on the instance? And some other notes that
I can't remember now. Choosing plugins versus augmentation largely boils down to personal
preference: do you want to be adding onto the API of this class, or do you want to be
segmenting this functionality?
For both plugins and augmentation, having the auxiliary code live in its own module,
or live in its own class, has value for maintenance going down the road. Again, we're talking
about the breaking up of one monolithic set of functionality into a series of more discrete
bits of functionality and then creating the relationships there.
Now we get into class extensions, which in my opinion are awesome. Let's get into that.
Class extensions do require Base. We're going to look at the two methods that are used with
class extensions by way of comparing it to the extend method.
In a typical case where you're extending Base, your constructor function does nothing but
pass off to the Base constructor. We saw enough of this in the library when we were doing
this in creating our own classes that we just said let's get rid of that. We need a name
to satisfy the contract for Base, but we're just going to be chaining the superclass,
so let's get rid of that awkward superclass chaining and then we'll add in this additional
argument here which is where the magic happens. With that, we have Y.Base.create.
Y.Base.create you will see all over the place in the library. We love this method. Actually,
we love this method because we haven't made another method that might be a little sexier,
but it does what we need it to do. We'll take a look at an example of a class generated
with Base.create.
The charts packages. There are a lot of individual classes in the charts package that take heavy
advantage of the class extensions. Here we have the line series which is extending Cartesian
series, and Cartesian series is an extension of Base. Here we have, in effect, an abstract
class, called Cartesian series. We're going to satisfy the contract of that abstract class
by mixing in this common functionality Y.Lines, and then gluing the Y.Lines code into the
Cartesian series code. The Y.Lines code is actually useful in other subclasses of Cartesian
series as well, and it might actually be useful outside of the Cartesian series family in
other classes as well.
You can see here, we'll just add this behavior and this behavior and these sets of behaviors
and then we'll glue them together. Since we have some common APIs and some common functionality,
it makes logical sense for maintainability to take that code and put it somewhere and
then incorporate that code. We don't have to do it using a build step, we can do it
using the APIs when you're defining your class. Let's take a look at the process of defining
line series and what it actually does, what Base.create does.
Base.create is going to create this sort of shell of a class, and its constructor is composed
of the chaining of its constructor and all of its class extension constructors. If I
had lines, or fills, or anything else in here, this constructor is going to be a call to
the Cartesian constructor followed by a lines constructor, fills constructor, all of those.
It goes through all of those constructors. In practice, class extensions tend not to
have any logic in their constructor. Prior to 3.4.1, they would because we didn't support
initializer chaining. As of 3.4.1, if you have an initializer defined on your class
extension, it will be chained in, which is really handy because initializer executes
after all of the attributes are set up.
Speaking of attributes, the next step is that it takes the attributes from Cartesian and
any attributes that you've specified in Y.Base as that fifth argument in the ATTRS collection
in there, and then all of the class extensions, and it just mixes them together into one object,
because this is a static collection of attributes. It does some friendly stuff in here in that
it takes the Cartesian and then here, since we actually had a type defined in both Cartesian
and in here, it's just going to mix them together. It combines the configuration for those attributes,
so you're not just clobbering that one with this one. It's not last one in wins - you
can contribute more and more to the attribute with other extensions. This can bite you,
but in practice this has actually been more handy than harmful to us. It's kind of a push.
Finally we get into the prototypes, where it does a similar thing of mixing in the individual
prototypes, starting by creating an instance of Cartesians. We have that prototype relationship
to its superclass, so officially it is still an instance of Cartesian, but all of the class
extensions are then mixed into the prototype directly. Like that alternate solution for
augment where we're just composing the prototype using all of the methods instead of doing
the lazy method linking, all of the instances of line series are going to benefit by having
all of its methods exist already on its prototype.
There's the special affordance then given to initializer, and also to destructor, that
if you define them in the class extensions then the prototype version of this Base.created
class is going to chain all of those initializers together.
When you create an instance of the line series, the three things that it does at construction
time are that it chains the constructors, it sets up the attributes, and then it calls
the initializers in order. It calls the initializers starting with the line series initializer,
followed by Y.Lines initializer, followed by all the other class extensions and initializers.
Class extensions basically break down into two categories: decoration and core functionality.
They're used for different purposes, basically. Class decoration would be if the core functionality
of this particular class is already well defined, instead of going with a plugin model I would
like these APIs and these attributes to exist on class instances directly. I'm choosing
to augment the prototype and augment the attribute collection directly for all instances of this
class. I prefer that for things that aren't going to get too big, or that aren't going
into systems that are needing a lot of plugins and a lot of features, because it's easier
to work with objects that have their own attributes and have their own methods.
The other way is to fill in core functionality. Remember I was mentioning earlier about the
Cartesian series basically being an abstract class? That's where this comes in. We're going
to satisfy the abstract class implementation.
The reason that having class extensions for core functionality is terribly useful is because it promotes
flexibility for an environment driven implementation. YUI has conditional loading of modules, for
example. You can create a class that is composed of a base abstract class followed by a number
of class extensions, and one of those class extensions could be the implementation specific
details of what it means to run in a Node.JS environment. Or I'm going to be delivering
this out to a mobile environment, so I want to have some lighter weight stuff going in
there. I can use dynamic loading to say in this case, in this environment, I'm going
to satisfy the abstract class with this particular implementation, and in the other case I'm
going to satisfy it with the other implementation.
At the end of the day, you have the abstract class and you have smaller bits of environment
specific code that are appropriate for you to be maintaining, because you know that I
have to change something for this class in this environment, so you can segment it out
per environment that way.
This is overlay. We like to show off overlay because this is all of overlay, and we just
think that's really cool. I'm actually curious what your response would be, but we are running
a little bit low on time. This is actually an implementation of all decorators. All of
these class extensions are generically useful to any widget. There was actually some discussion
about whether or not we should bother creating an overlay class, because people can just
create their own instance of whatever overlay-like class they want that's composed of whichever
pieces of this functionality they want, which is still true. You can still do that by, I
think, Panel and Tooltip and other examples that'll just use a couple of those bits of
functionality. These are all features, these are all decoration. This isn't core functionality
to overlay. Core functionality in overlay is just widget.
Contrast that with Slider. I failed to mention the other method for working with class extensions.
Here we have a SliderBase which is an abstract class which we satisfy with that Y.Slider
value range implementation for adding the value attribute, and for tying the movement
of the thumb to updating the value attribute. But it would be awkward to have that functionality
live on a namespace like in a plugin, right? So it defines the class.
But then Y.Base.mix is the method that we can then say here's some additional behavior,
and today I'm going to add this to my class. This actually modifies Y.Slider, and it adds
the behavior onto the prototype, and it updates the constructor and the construction logic
to now include the clickable rail constructor into the constructor chaining, and the initializer
into the initializer chaining. It mixes in the attributes and all of that. So basically
we are dynamically changing and enhancing this Slider class at run time. You can get
into all sorts of fun when you have packaged up features and are adding them into a class
and making it more feature rich, I guess.
The extension pros and cons. I went over this already, but it strongly encourages the segmentation
of code into implementation specific stuff. In particular, it's well suited for dynamically
reacting to environments. If you prefer the APIs live on that class for features then
it's a good option. Otherwise, for doing the abstract class pattern, you would want to
be using class extensions for that.
Because of the nature of the class extensions being aggregated onto that class, it allows
you to emulate other patterns for assembling your class together. If you want to emulate
an MVC breakdown in the logic for your widget, for example, you can have the class extension
that does the model stuff, you can have the class extension that does the view stuff,
you can have the class extension that does the controller stuff, and then they are Base.created
onto a class. You then instantiate it, and it has all of the logic and the APIs there
in one object. You use that one object instead of working with three disparate objects.
The cons. It saying that it requires Y.Base is a con bothers me. I really like Y.Base.
There is some initialization overhead, because it is going through all of the chaining of
the constructors, the chaining of the initializers. And you can't do this to instances like you
can do with plugins. It doesn't work on Nodes. And other things that we talked about with
augment where you're going to run into naming collisions.
I think the big takeaway, really, from the session should be: look at extensions and
plugins, play around with them, see what feels best for you and your particular style. If
you want to use class-based plugins so that all instances have this additional feature
set, but that feature set exists in a namespace like it does in patterning after the DOM...
In particular, look at class extensions for doing class composition for segmenting out
reusable bits of code.
Yeah, I think that's largely it.
MVC. Since we are out of time, and since Eric is doing a talk on MVC tomorrow, I am going
to forgo all of my slides and say go see Eric's talk because he knows more about it, because
he wrote it.
Wait! Use Base. If you don't know about the IRC channel, we're in there all the time.
Great community in there. All right.