Luke Smith: Class Inheritance and Composition Patterns in YUI
Articles Blog

Luke Smith: Class Inheritance and Composition Patterns in YUI

September 21, 2019


>>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. OK? 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 methods. 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 constructor. 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.>>AUDIENCE MEMBER: OK.>>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 say. 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 plugins. 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. [applause] 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.

Only registered users can comment.

  1. Y.Object – is this what the new Object.create does? Create a new object from an old object? I'm new to JavaScript so excuse me if I'm wrong.

Leave a Reply

Your email address will not be published. Required fields are marked *