1. Trang chủ >
  2. Công Nghệ Thông Tin >
  3. Kỹ thuật lập trình >

4 Class/module design and naming

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (5.14 MB, 519 trang )

Class/module design and naming



Mix-ins and/or inheritance

Module mix-ins are closely related to class inheritance. In both cases, one entity (class

or module) is establishing a close connection with another, by becoming neighbors

on a method-lookup path. In some cases, you may find that you can design part of

your program either with modules or with inheritance.

Our CargoHold class is an example. We implemented it by having it mix in the

Stacklike module. But had we gone the route of writing a Stack class instead of a

Stacklike module, we still could have had a CargoHold. It would have been a subclass

of Stack, as illustrated in listing 4.14.

Listing 4.14

CargoHold, inheriting from Stack instead of mixing in Stacklike

class Stack

attr_reader :stack

def initialize

@stack = []


def add_to_stack(obj)



def take_from_stack




class Suitcase


class CargoHold < Stack

def load_and_report(obj)

print "Loading object "

puts obj.object_id



def unload




From the point of view of an individual CargoHold object, the process works in

listing 4.14 exactly as it worked in the earlier implementation, where CargoHold

mixed in the Stacklike module. The object is concerned with finding and executing

methods that correspond to the messages it receives. It either finds such methods on

its method-lookup path, or it doesn’t. It doesn’t care whether the methods were

defined in a module or a class. It’s like searching a house for a screwdriver: you don’t

care which room you find it in, and which room you find it in makes no difference to

what happens when you subsequently employ the screwdriver for a task.

There’s nothing wrong with this inheritance-based approach to implementing

CargoHold, except that it eats up the one inheritance opportunity CargoHold has. If

another class might be more suitable than Stack to serve as CargoHold’s superclass

Licensed to sam kaplan



Modules and program organization

(like, hypothetically, StorageSpace or AirplaneSection), we might end up needing

the flexibility we’d gain by turning at least one of those classes into a module.

No single rule or formula always results in the right design. But it’s useful to keep a

couple of considerations in mind when you’re making class-versus-module decisions:

Modules don’t have instances. It follows that entities or things are generally best

modeled in classes, and characteristics or properties of entities or things are

best encapsulated in modules. Correspondingly, as noted in section 4.1.1, class

names tend to be nouns, whereas module names are often adjectives (Stack

versus Stacklike).

A class can have only one superclass, but it can mix in as many modules as it wants. If

you’re using inheritance, give priority to creating a sensible superclass/subclass

relationship. Don’t use up a class’s one and only superclass relationship to

endow the class with what might turn out to be just one of several sets of characteristics.

Summing up these rules in one example, here is what you should not do:

module Vehicle


class SelfPropelling


class Truck < SelfPropelling

include Vehicle


Rather, you should do this:

module SelfPropelling


class Vehicle

include SelfPropelling


class Truck < Vehicle


The second version models the entities and properties much more neatly. Truck

descends from Vehicle (which makes sense), whereas SelfPropelling is a characteristic of vehicles (at least, all those we care about in this model of the world)—a characteristic that is passed on to trucks by virtue of Truck being a descendant, or specialized

form, of Vehicle.

Another important consideration in class/module design is the nesting of modules and/or classes inside each other.


Nesting modules and classes

You can nest a class definition inside a module definition, like this:

module Tools

class Hammer

Licensed to sam kaplan



To create an instance of the Hammer class defined inside the Tools module, you use the

double-colon constant lookup token (::) to point the way to the name of the class:

h = Tools::Hammer.new

Nested module/class chains like Tools::Hammer are sometimes used to create separate namespaces for classes, modules, and methods. This technique can help if two

classes have a similar name but aren’t the same class. For example, if you have a

Tool::Hammer class, you can also have a Piano::Hammer class, and the two Hammer

classes won’t conflict with each other because they’re nested in their own namespace

(Tool in one case, Piano in the other).

(An alternative way to achieve this separation would be to have a ToolsHammer class

and a PianoHammer class, without bothering to nest them in modules. But stringing

names together like that can quickly lead to visual clutter, especially when elements

are nested deeper than two levels.)

Class or module?

When you see a construct like Tools::Hammer, you can’t tell solely from that construct what’s a class and what’s a module—nor, for that matter, whether Hammer is

a plain old constant. (Tools has to be a class or module, because it’s got Hammer

nested inside it.) In many cases, the fact that you can’t tell classes from modules in

this kind of context doesn’t matter; what matters is the nesting or chaining of names

in a way that makes sense. That’s just as well, because you can’t tell what’s what

without looking at the source code or the documentation. This is a consequence of

the fact that classes are modules—the class Class is a subclass of the class

Module—and in many respects (with the most notable exception that classes can be

instantiated), their behavior is similar. Of course, normally you’d know, either

because you wrote the code or because you’ve seen documentation. Still, it pays to

realize that the notation itself doesn’t tell you everything.

We’ll look further at nested classes, modules, and other constants in the next chapter,

when we talk in more detail about the subject of scope. Meanwhile, note that this ability to nest modules and classes inside each other (to any depth, in any order) gives you

yet another axis along which you can plan your program’s design and structure.



This chapter has been both a companion to and a continuation of the previous chapter on classes. We’ve looked in detail at modules, which are similar to classes in that

they bundle methods and constants together, but which can’t be instantiated. You’ve

seen examples of how you might use modules to express the design of a program.

We’ve taken an object’s-eye view of the process of finding and executing a method in

response to a message, or handling failure with method_missing in cases where the

message doesn't match a method. We’ve also looked at some techniques you can

use—including nesting classes and modules inside each other, which can have the

Licensed to sam kaplan



Modules and program organization

benefit of keeping namespaces separate and clear. Finally, we discussed aspects of

modular organization, including nesting of modules and classes, and how to keep

things clear as you start to have more classes and modules in your code.

It’s particularly important to take on board the way that objects resolve messages

into methods: they go on a search through a succession of classes and modules.

Objects don’t, themselves, have methods, even though phrasing it that way is sometimes a handy shortcut. Classes and modules have methods; objects have the ability to

traverse classes and modules in search of methods.

Now that we’re nesting elements inside each other, the next topic we should and

will examine in detail is scope: what happens to data and variables when your program

moves from one code context to another. We’ll look at scope in conjunction with the

related, often interwoven topic of self, the default object.

Licensed to sam kaplan

The default object (self),

scope, and visibility

In this chapter

The role of the current or default object, self

Scoping rules for local, global, and class


Constant lookup and visibility

Method access rules

In describing and discussing computer programs, we often use spatial and, sometimes, human metaphors. We talk about being “in” a class definition or returning

“from” a method call. We address objects in the second person, as in

obj.respond_to?("x") (that is, “Hey obj, do you respond to ‘x’?”). As a program

runs, the question of which objects are being addressed, and where in the imaginary space of the program they stand, constantly shifts.

And the shifts aren’t just metaphorical. The meanings of identifiers shift too. A

few elements mean the same thing everywhere. Integers, for example, mean what

they mean wherever you see them. The same is true for keywords: you can’t use


Licensed to sam kaplan



The default object (self), scope, and visibility

keywords like def and class as variable names, so when you see them, you can easily

glean what they’re doing. But most elements depend on context for their meaning.

Most words and tokens—most identifiers—can mean different things at different

places and times.

This chapter is about orienting yourself in Ruby code: knowing how the identifiers

you’re using are going to resolve, following the shifts in context, and making sense of

the use and reuse of identifiers and terms. If you understand what can change from

one context to another, and also what triggers a change in context (for example, entering a method-definition block), you can always get your bearings in a Ruby program.

And it’s not just a matter of passive Ruby literacy: you also need to know about contexts

and how they affect the meaning of what you’re doing when you’re writing Ruby.

This chapter focuses primarily on two topics: scope and self. The rules of scope

govern the visibility of variables (and other elements, but largely variables). It’s important to know what scope you’re in, so that you can tell what the variables refer to and

not confuse them with variables from different scopes that have the same name, nor

with similarly named methods.

Unlike scope, self isn’t so much a concept as an object. But self changes in the

course of a program. At every moment, only one object is playing the role of self. But

it’s not necessarily the same object from one moment to the next. The self in Ruby is

like the first person or “I” of the program. As in a book with multiple first-person narrators, the I role can get passed around. There’s always one self, but who it is—what

object it is—will vary.

Between them, self and scope are the master keys to orienting yourself in a Ruby

program. If you know what scope you’re in and know what object is self, you’ll be able

to tell what’s going on, and you’ll be able to analyze errors quickly.

The third subtopic of this chapter is method access. Ruby provides mechanisms for

making distinctions among access levels of methods. Basically, this means rules limiting the calling of methods depending on what self is. Method access is therefore a

meta-topic, grounded in the study of self and scope.

Finally, we’ll also discuss a topic that pulls together several of these threads: toplevel methods, which are written outside of any class or module definition.

Let’s start with self.


Understanding self, the current/default object

One of the cornerstones of Ruby programming—the backbone, in some respects—is

the default object or current object, accessible to you in your program through the

keyword self. At every point when your program is running, there is one and only

one self. Being self has certain privileges, as you’ll see. In this section, we’ll look at

how Ruby determines which object is self at a given point and what privileges are

granted to the object that is self.

Licensed to sam kaplan


Understanding self, the current/default object


Who gets to be self, and where

There is always one (and only one) current object or self. You can tell which object it

is by following a small set of rules. These rules are summarized in table 5.1; the table’s

contents will be explained and illustrated as we go along.

To know which object is self, you need to know what context you’re in. In practice,

there aren’t many contexts to worry about. There’s the top level (before you’ve

entered any other context, such as a class definition). There are class-definition

blocks, module-definition blocks, and method-definition blocks. Aside from a few subtleties in the way these contexts interact, that’s about it. As shown in table 5.1, self is

determined by which of these contexts you’re in (class and module definitions are

similar and closely related).

Figure 5.1 gives you a diagrammatic view of most of the cases in table 5.1. Both

show you that some object is always self and that which object is self depends on where

you are in the program.

Table 5.1

How the current object (self) is determined



Which object is self?

Top level of program

Any code outside of other blocks

main (built-in top-level default


Class definition

class C


The class object C

Module definition

module M


The module object M

Method definitions

1. Top level (outside any definition block)

Whatever object is self when

the method is called; top-level

methods are available as private methods to all objects

def method_name


2. Instance method definition

in class

An instance of C, responding

to method_name

class C

def method_name


3. Instance method definition

in module

module M

def method_name


4. Singleton method on specific


I. Individual object extended

by M

II. Instance of class that

mixes in M


def obj.method_name


Licensed to sam kaplan

Xem Thêm
Tải bản đầy đủ (.pdf) (519 trang)