AS3 101: OOP Inheritance, Setters & Getters – Basix

When last we talked, we had a Button class that could be used to quickly create a button of a certain style. However, I also asked you to ignore certain aspects of the code. We're going to start this session off with a little resolution: it's time to explain what extends does.

Over the course of this tutorial, we'll expand that Button class, gain a deeper understanding of what's actually going on, and learn a few more Object-Oriented Programming terms and techniques, namely inheritance. The Button class will get better and more useful.


Step 1: Inheritance

One of the powerful features of OOP is inheritance. First things first, that's inheritance in the sense of genealogy, not inheritance in the sense of receiving sums of money from a deceased uncle.

In a nutshell, inheritance enables a given class to take an existing class and do everything that it can do, but with modifications and/or additions. In other words, suppose class A does two things, called foo and bar. If class B inherits from A, then it will automatically also do foo and bar. B can also then define an extra thing it can do, called baz. It can also choose to still do something called bar, but change the internal behavior a bit.

A practical illustration will help explain this.


Step 2: E.g: The DisplayObject

In ActionScript 3, there is a class named DisplayObject. This class defines properties and methods relevant to any visual element: x and y, width and height, alpha, filters, mask, rotation, getBounds(), hitTestPoint(), etc (see the documentation for a full list). Essentially, any task appropriate for all types of displayable objects are defined here.

There are a number of classes that extend DisplayObject. One of these is Shape. All that Shape does is define a graphics property, which enables programmatic vector drawing. Basically, Shape is a displayable object that specializes in vector graphics. And because Shape extends DisplayObject, it inherits all of those other, more general properties and methods, and a Shape is said to be a kind of DisplayObject (see the documentation for the Shape class).

Another subclass of DisplayObject is Bitmap. Bitmap is sort of the pixel analog of Shape; it merely adds a few properties relevant to work with bitmaps (pixels) with ActionScript. Again, though, because it extends DisplayObject it can also be positioned, rotated, filtered, hit-tested, etc, just like DisplayObject, and thus Bitmap is said to be another kind of DisplayObject (see the documentation for the Bitmap class).

What's more, a third class that inherits from DisplayObject is InteractiveObject. With InteractiveObject, there are half-a-dozen properties added pertaining to context menus, keyboard navigation, mouse enablement. The big thing InteractiveObject adds is a whole slew of events that it is capable of dispatching, from mouse rollovers to keyboard focus to any kind of possible user input. And, once again, an InteractiveObject is a kind of DisplayObject and so it can also be positioned, et al (documentation for the InteractiveObject class).

The following graph shows the relationships of the various classes we just discussed (along with one we haven't: Video. Hopefully the purpose of this class is obvious).

In a graph such as this, a line with an arrowhead at one end indicates an inheritance relationship. The box with the tail of the arrow is the class that extends the box with the head of the arrow.


Step 3: The Inheritance Structure

At this point, we've looked at four classes: one superclass, and three of its subclasses. Note that while all three subclasses share some common functionality (position, rotation, etc.), all of the overlap is defined in DisplayObject. The Shape has a graphics property, Bitmap has a bitmapData property, and InteractiveObject dispatches a rollOver event, and those are unique to those classes. A Bitmap can't be programmatically drawn into, and a Shape can't dispatch a rollOver event.

This is a common use for inheritance: to consolidate functionality that is common to a family of classes. All of these display objects need to be positioned, so rather than putting positioning code in each of the classes, Adobe put the code in a super class from which the all other display objects can inherit.

The list goes on: InteractiveObject has subclasses of its own. TextField is an impressive class that extends InteractiveObject but also adds many properties and methods dealing with setting, getting, and styling text. Not only does it extend InteractiveObject, and therefore inherits all of the events and things defined there, but it (indirectly) inherits from DisplayObject, so it too can be positioned, rotated, etc. That functionality continues down the family tree (see documentation for the TextField class).

Another class called DisplayObjectContainer is also a subclass of InteractiveObject, and it adds functionality for managing other display objects that can be contained within another display object, like addChild() and swapChildren() (child being the general term used for a display object that is contained by another display object) (see documentation for the DisplayObjectContainer class). Furthermore, Sprite extends InteractiveObject, adding dragging capabilities along with some interactivity conveniences and a graphics object (see documentation for the Sprite class).

The following image illustrates the entire inheritance tree as we've discussed to far.


Step 4: The MovieClip as Example

And last but not least, MovieClip extends Sprite, defining the ability to work with the timeline (with properties like currentFrame and methods like play() and gotoAndStop()). By the time we get to MovieClip, it has a lineage that goes up through Sprite to DisplayObjectContainer to InteractiveObject to DisplayObject, thus making it capable of:

  • Position, rotation, filters, and hit tests (thanks to DisplayObject)
  • Dealing with interactivity, namely dispatching events related to user input (thanks to InteractiveObject)
  • Containing other display objects (thanks to DisplayObjectContainer)
  • Working with dragging and being drawn into (thanks to Sprite)
  • Managing its timeline (thanks to MovieClip itself)

That's a lot of functionality, but the hierarchy of inheritance for all display objects enables a lot of code reuse, so really MovieClip isn't adding all that much to the table. It's just the next logical step for adding features (see documentation for the MovieClip class).

It's also worth noting that DisplayObject is itself a subclass: it extends EventDispatcher so that it's capable of dispatching events and working with the standard AS3 event model. MovieClip has quite a heritage.

For an overview on Flash's display list, please see Adobe's documentation page on Display Programming, as well as my previous tutorial covering the display list.

The following illustration summarizes the hierarchy of inheritance that we've discussed in the previous three steps, also adding a few classes we haven't talked about:

Note that this illustration is not comprehensive; there are incomplete lists of properties and methods, and in fact there are entire classes missing. The purpose is to provide a compact representation of the extensive DisplayObject hierarchy.


Step 5: Extending MovieClip

So, when we write this:

We're saying that the class we're writing will actually merely define the extra properties and methods that we'll sort of graft on the existing MovieClip class. That is, Button101 is a MovieClip, only with a little extra built-in capability.

In the end, that means that from within the Button101 class, we can use DisplayObject properties and methods as if we wrote them ourselves, like we do here:

That addChild() method is defined by DisplayObjectContainer, but ultimately we inherit from DisplayObjectContainer so we can use it.

Similarly, any bit of code that uses a Button101 object can basically treat it like any other display object, like we do here, in DocumentClass:

So, when we create the Button101 object, we can think of it as just a specialized MovieClip. It's not unlike how you create specialized MovieClips in the Flash IDE and store the symbols in the library. The only real difference is that in that case, you use the graphical tools instead of writing a bunch of code.

In fact, the double whammy here is that DocumentClass itself extends MovieClip, and so it too inherits all of these methods and properties, which is how we have access to an addChild() method in the first place.

(Note that you can combine those two approaches and write some custom code that is linked to a symbol in the library, but we won't address that technique in this tutorial.)


Step 6: Now What?

OK, that was quite a bit of theory, so take a quick breather if you need it. The point is to come to grips with the terminology and techniques, but not necessarily master it in one go. Object-Oriented Programming is a significant mental leap, so don't expect it all to sink in right away.

In fact, if you need to, just stop reading right now and come back to this after your head stops spinning. Or even re-read the previous several steps to make sure you've got it.

Even if you're ready to move on right now, the sole purpose of this step is to not think so much...

Carry on when you're ready.


Step 7: Mapping a Button to an Action

Right now, our two buttons both have their CLICK events handled by the same method in the document class. Sometimes it's useful to have two buttons do the same thing to give the user a few choices, but most often different buttons do different things. How can we set up multiple actions for multiple buttons?

There are a few approaches to this problem, and I'll outline a few.


Step 8: Option 1 Multiple Event Handlers

First, and probably the most obvious, is to simply set up a second event handling method. For instance (code abbreviated to focus on the relevant bits...feel free to integrate into your working project to test it out, but for the purposes of continuing tutorial this isn't officially code to enter into the document class):

This is probably the most common solution to this situation, especially when two buttons perform rather disparate functions. When one button opens an informational overlay, and another purchases a product, it makes sense to do what probably comes naturally and make separate functions.


Step 9: Option 2 Switch/Case

However, when buttons perform very similar functions, such as multiple product thumbnails needing to open up a product detail, just with different content, then making multiple handlers can be tedious at best, and can lead to hard-to-manage code at worst. In this case, it's probably be better to utilize a conditional statement or a Dictionary (see AS3 101 - Branching for more in-depth discussions of both of these techniques).

If the thumbnails were dynamically generated (using external data and a loop...see AS3 101 - Loops and AS3 101 - XML), that makes it darn near impossible to create a unique event handler for each button, and even makes it pretty hard to pull off the above trick (although a Dictionary would work well).


Step 10: Option 3 Objects That Store Data

However, there is another option that starts to become attractive as OOP techniques are utilized. It's a step further than the last option; instead of comparing the event target to a known list of possible targets, we simply ask the target for the relevant piece of information.

Without any further modification to the Button101 class, we can illustrate this rather simply. Suppose in our trace message, it was our goal to output the x position of the button that was clicked, something like this:

We can do that very easily by simply referencing the x property of the target itself.

Check that out, one line! No need for dictionaries, switch statements or multiple, near-identical event handlers.

Remember that excessively long discussion about inheritance? Of course you do. Because e.target is a Button101 object, and because a Button101 object is ultimately a DisplayObject, we have an x property, even though we never defined that property in our Button101 class.

This becomes even more powerful when we realize we can create custom properties on the Button101 class (or any custom class) in which its objects can store data. Suppose that, for instance, instead of the x value of the button, we wanted to navigate to one URL or another. And if each button instance had its own URL data stored in a url property, we can handle the execution of the click very easily.

If you're thinking that this could be as simple as:

Then you're correct. However, to get that url property to exist in the first place, we have some work to do in the Button101 class. And before we do that, we need to talk about access modifiers.


Step 11: Public Access

The time has come to address these public and private words I've been telling you to ignore (finally!). These are called access modifiers and define how a property or method can be accessed (or not) from other parts of the program.

There are four modifiers:

  • private
  • protected
  • internal
  • public

The order of that list is intentional; the amount of access increases as we go down the list. Here's a quick run down of the access types:

private is the least accessible. The only object that will be able to access a private property or method is the one that owns it. For example, the two properties of Button101, label and bgd, are private, and so the only object that can work with those items is the Button101 instance itself. Note than even two instances of the same class have mutually exclusive access to their own private properties; button can access its label property, but it can't access button2's label property, and vice versa.

protected is very similar to private, only that it allows any subclass to also access the property or method. So if we created a subclass of Button101, that subclass would not be able to access the label or bgd properties (because they are private), but could access another property that was defined as protected.

internal expands the accessibility a little more. It grants access to any other object that is in the same package as the class. This one is used far less that the others and only really makes sense once you understand packages, so we won't be revisiting this one. Note, however, that internal is the default access if you forget to specify (don't ask me why).

public is the most accessible. Basically, any object can access the property or method. If another object has access to a Button101 object, then it can call the setLabel() method.

ActionScript 3 will enforce these rules. If you make a property private and then try to access it from a subclass, then the compiler will complain and you'll get an error message. The idea is that you set the rules down as you craft your class ("this should be accessible, that shouldn't") and then you need to stick to them.


Step 12: Make a private _url property

In Button101, declare a third property, _url, to hold a URL string.

What's up with the underscore? This is a convention that I've been glossing over until we've had a discussion on access modifiers. It's not required, but it's quite common to declare your private and protected properties with names that begin with underscores. Part of this has to do with convention, part to do with being able to recognize at a glance an object property versus a local function variable (remember scope?), and a lot to do with the topic of the next step.

I would advise you to read up on this Quick Tip on how to choose an access modifier for your properties and methods. In a nutshell, though, the main takeaway is to always declare properties as private or possibly protected, so as to make sure they don't get changed without the object's approval. However, we still need a way to access the data, and this is where setters and getters come in.


Step 13: Setters and Getters

OK, so you have a private property, and you'd like to make it public. You could just change the "private" to "public" and be done with it, except you recall some crotchety tutorial author telling you to never do that. What do you do? You use setters and/or getters.

Setters and getters are, simply put, methods that either set the value of some property, or get the value of some property. There are two kinds of setter/getter methods to keep in mind: implicit and explicit.

Explicit setters/getters are what you're probably already thinking they are: regular old methods. Given a private property "_url" of type String, a setter might look like this:

And a getter might look like this:

Note that the setter receives a single parameter of the same type as the property, simply sets the property's value to that incoming value, and returns nothing. The property is set, and that's it.

Similarly, the getter does a single thing: it returns the value held by the property, and takes no arguments.

To use these, you'd simply call them like any other method. If we had an instance of this object in the variable button we could do this:

And that should not be a surprise. So why am I going into so much detail on this topic? Because of the implicit setters/getters.

Most OOP languages afford this capability, and ActionScript 3 does so with the set and get keywords. These are extra modifiers you place in your method declaration that makes these method implicit setters and getters. We'll get to what that means in a second, but for now, add this setter to the Button101 class:

Notice it looks pretty darn close to the previous setter, only the word "set" is out there by itself, between the word "function" and the name of the function. This is a subtle detail; the word "set" is not part of the function name. It's an extra keyword that turns a method into an implicit setter. Otherwise, though, it's purpose should be obvious.

Next, add this getter:

Again, it's almost identical to the previous getter, only with the "get" keyword in between the "function" keyword and the function name (if you added the previous explicit examples, please remove them to prevent confusion).

Now, here's where things get interesting. Back in the DocumentClass, let's set the url, and then we'll trace it right away to (a) make sure it worked, and (b) see what using an implicit getter looks like.

At the end of the constructor, after both buttons have been created, add the following lines:

That's a significant difference; it looks like you're working with a property, not a method! That's the magic of implicit setters and getters. They take a method and make it look like a property. Working with it is just like setting or getting the property, but inside the class we have a function.

Now, why go to this trouble? There are, naturally, a few compelling reasons, a few of which we'll explore in the next few steps as we add more setters and getters.


Step 14: Get the URL

Let's go back to the original problem of knowing what to do when a button is clicked. Earlier, I mentioned that could go in the document class; let's make that a reality.

In DocumentClass.as, update your onButtonClick() method to look like this:

And in the constructor, make sure the url properties are set on both buttons, but go ahead and remove the traces:

Finally, you'll need to add the following import line at the beginning:

For reference, the complete DocumentClass should look like this:

And just to be sure, here is the complete Button101 class:

Go ahead and try it out. Clicking on the buttons will take you to one of two web pages.


Step 15: Setting and Getting the Label

Another reason to roll with setters and getters is the ability to make a complex task simple to execute. For example, let's add the ability to update the label using a setter. We actually already did this in the previous tutorial, but we didn't use the term setter. We'll update the existing setter to be implicit and add a getter.

In Button101, you'll see this method:

Remove that, and add this:

This lets us, from elsewhere in the program, work with a "label property" that simply sets the label text of the button. What's really going on behind the scenes, though, is a little more complicated; you need to work with a TextField and a String, not just a String.

This is encapsulation again. You might think, "why not just make the labelField property public -- or, OK, make a setter and getter for the labelField -- and then do something like button.labelField.text = "Button 1" ?"

That would certainly work, but do you really need to expose the labelField to the rest of the world? Assuming that the general style of the button is set in stone, there's really no need to work with that TextField outside of the Button101 class, except to set the label text. Exposing the labelField would allow another object to not only set the text, but to also position, rotate, style, or even hide the label. This is probably not desirable; even if you're a good citizen and wouldn't intentionally write code that does that, it's better to protect yourself from accidental changes. Let the compiler warn you if tried a stunt like that, as opposed to letting the application run and having your label mysteriously rotated.

In DocumentClass, make sure to change the lines that call setLabel to label = :


Step 16: Adjusting the Width and Height with Overrides

We'll examine another argument in favor of the setter. We might want to adjust the width or height of the button. Since the button class extends MovieClip, it already has width and height properties, which is handy, except it's not quite what we want. Check out what happens if we set it:

The above SWF was the result of setting the height property of the left-hand button object to 100. If you want to try this yourself, just add the following line at the end of the constructor of your DocumentClass:

Now, this isn't terrible, but I would argue that it's undesirable. It's happening because this is the default behavior for scaling MovieClips (and all DisplayObjects, actually, since height is defined in DisplayObject). It's merely a linear stretching of the artwork contained within, text fields included. The text at least is not distorting, but its size is disconcerting.

We can write our own method to set the width, such as public function setWidth(), but there's an even better way. We can override the behavior of a method defined in a superclass. We can, as a subclass, actually fine-tune the functionality to our needs by changing the definition of what an existing method does. It looks like this:

It's a standard method declaration, except with an extra keyword at the front: override. Note that the order of "public" and "override" is actually interchangeable, but most code I've seen tends to put the "override" word first. It doesn't matter, but it's advisable to pick a standard for yourself (or your company) and stick with it.

Let's add some functionality. Keep in mind that we are completely replacing the existing set width method, so we need to make sure we cover all the bases. As it turns out, it's fairly simple. Instead of stretching the main MovieClip container, and all of the children within it by default, we'll actually stretch each of the children individually. It's almost strange that it works, but it does:

By setting the width and height of the text field explicitly, we avoid the awkward text scaling. We can also add in a line of logic to reposition the label TextField so that it remains vertically centered.

Note that we are not writing a getter; the behavior defined by DisplayObject is just fine, and there is no need to override it.

To try it out, simply add (or keep) the line that sets the button's height to 100 pixels in the DocumentClass.

There are a number of other reasons why you'd want to start using setters and getters. I've listed five of them here.


Step 17: Adjusting the Width Automatically

One last item on the setter technique. Here's the goal: to make the button automatically expand in width if the label is too long. So, if you set the label to "Button 1", there is plenty of space, and the text is nicely centered. But if you were to set the label to "Press This Button To Hack the Gibson," that would be too long, and the text would get truncated.

This sort of behavior is quite easy to manage with the use of a setter. When setting the label text, you can also do a little post-processing on the text field and background to make sure they are sized appropriately. Rework your label setter so that it looks like this:

The logic is relatively straightforward, but here's a rundown in case you haven't done this sort of thing before. First, we figure out what the width should be. By using the Math.max() function, we can pass in two numbers and get the higher one back. One of those numbers is 200, which I'm defining as our minimum width. The other number is the width of the text in the text field, plus a 40 pixel margin (20 pixels on either side). So if the text width is small, we'll get 200 back and even with a short label we'll have a 200-pixel-wide button. If the text width is large, however, we'll get the text width (plus a margin) back, and then we'll use that value instead.

We finally simply set the width of the button to this value, calling the setter we just wrote in the last step. Try it out by setting the label of the second button to an unusually long bit of text:

Of course, the actual logic you take here could be any number of things. You might find it more useful to implement a "minimumWidth" property that specifies the minimum number of pixels (instead of 200), or an "autoFit" property that signals whether or not to adjust the width automatically, to make the Button101 a little more flexible. I leave these options to the reader as further exercises.

But those options bring up a good point. Adding the necessary code to implement such features will start to make your class more complex. That in itself isn't really a bad thing, but it does herald the truth that the more features you have, the more complex your code. One of the hallmarks of good Object-Oriented Programming is that complexity lies in the ways in which several objects are connected together, not in the logic of a single class. Some classes will be, by necessity, rather complex, but as a rule, more and smaller classes are preferable over fewer and larger classes.

Take the aforementioned DisplayObject hierarchy as an example. The MovieClip class (or Button101 class, for that matter) is in itself rather complex, given that it can dispatch events, position itself, handle interactivity, contain other DisplayObjects, be dragged, and manage a timeline, but remember that a vast majority of that complexity is defined by classes other than MovieClip. Remember, everything other than the timeline-related stuff is defined in a super class. The DisplayObject hierarchy is a rather well-thought out system of classes, each one taking on the responsibility of a focused task.

This is a point that's difficult to execute well, so I just want to mention it, plant that seed, and hopefully seep into your sub-conscience for a more opportune time.


Summary

In this installment, we took a long hard look at primarily two things: inheritance; and setters and getters.

Inheritance is the mechanism by which one class can leverage most everything already defined in another class, but sub-classing it. The subclass can then add more functionality or even redefine existing functionality via overrides.

Setters and getters are any method than can be used to access a private property. Implicit setters and getters look like properties themselves, but are really special methods. They can be useful for controlling access and performing validation, or executing more complex logic behind the simple setting of a property.

There is still much to learn on this topic, so stick around for the forthcoming Part 3. You're doing great, and thank you for sticking it out this far!

Tags:

Comments

Related Articles