Tell Don't Ask (1997)

栏目: IT技术 · 发布时间: 4年前

内容简介:Alec Sharp, in the recent book,points up a very valuable lesson in few words:

Alec Sharp, in the recent book Smalltalk by Example

,

points up a very valuable lesson in few words:


Procedural code gets information then makes decisions.

Object-oriented code tells objects to do things.

— Alec Sharp

That is,

you should endeavor to tell objects

what you want them to do; do not ask them questions about their

state, make a decision, and then tell them what to do.

The problem is that,

as the caller, you should not be making decisions based on the

state of the called object that result in you then changing the

state of the object. The logic you are implementing is probably

the called object’s responsibility, not yours.

For you to make decisions outside the object violates its encapsulation.

Sure , you may say,

that’s obvious. I’d never write code

Still, it’s very easy to get lulled into examining some

referenced object and then calling different methods based on the

results. But that may not be the best way to go about doing it. Tell

the object what you want. Let it figure out how to do it. Think

declaratively instead of procedurally!

It is easier to stay out

of this trap if you start by designing classes based on their

responsibilities, you can then progress naturally to specifying

commands that the class may execute, as opposed to

queries that inform you as to the state of the object.

Just the Data

The main purpose of this exercise is to ensure a correct division of

responsibility that places the right functionality in the right class

without causing excess coupling to other classes.

The biggest danger here is that by asking for data from an object,

you are only getting data. You’re not getting an object—not

in the large sense. Even if the thing you received from a

query is an object structurally (e.g., a String ) it is

no longer an object semantically . It no longer has any association

with its owner object. Just because you got a string whose contents

was “ RED ”, you can’t ask the string what that means. Is it the owners

last name? The color of the car? The current condition of the tachometer?

An object knows these things, data does not.

The fundamental principle of Object Oriented programming is the unification

of methods and data. Splitting this up inappropriately gets you right

back to procedural programming.

Invariants aren’t enough

Every class has invariants—things that must

always be true. Some languages (such as Eiffel ) provide direct support for specifying and checking invariants. Most

languages do not, but that only means that the invariants are not

stated—they still exist. For instance, an iterator has the

invariant that (using Java as an example):

hasMoreElements() == true // implies that: nextElement() // will return a value

In other words, if hasMoreElements() is true, then attempting

to get the next element must succeed, or something is seriously

broken. If you are running multi-threaded code without the proper

synchronization (locking), it may well be that the above invariant doesn’t

hold: some other thread grabbed the last element before you did.

The invariant doesn’t hold; so something is wrong—you have a bug.

According to Design by Contract, as long as your methods

(queries and commands ) can be freely intermixed,

and there is no way to violate the class invariant by doing so, then

you are ok. But while you are maintaining the class invariant, you

may have also dramatically increased the coupling between

the caller and the callee depending on how much state you have exposed.

For instance, suppose you have a container object C .

You could expose iterators for the held objects in this container,

as many of the JDK core routines do,

or you could provide a method that would run some function over

all members in the collections for you. In Java you might declare

this as something like:

public interface Applyable { public void each(Object anObject); } … public class SomeClass { void apply(Applyable); } // Called as: SomeClass foo; … foo.apply( new Applyable() { public void each(Object anObject) { // do what you want to anObject } });

(Forgive the neologic barbarism of “Apply-able”; we’ve found it handy

to name interfaces as “-able”, but English isn’t as cooperative as

one would like).

This is easier to code in languages with function pointers, and even

easier in Perl or Smalltalk where such concepts are built in, but you

should get the idea: “run this function over all contained items, I

don’t care how.”

You can achieve the same results both ways, either via an apply

sort of method or via iterators. The choice really comes down to

how much coupling you are willing to have:

To minimize coupling, expose the minimum amount of state necessary.

As shown here, apply exposes less state than exposing

an iterator does.

Law of Demeter

So we’ve decided to expose as little state as we need to in order

to accomplish our goals. Great! Now within our class

can we just starting sending commands

and queries to any other object in the system will-nilly? Well, you could,

but that would be a bad idea, according to the

Law of Demeter . The Law of Demeter tries to restrict class interaction

in order to minimize coupling among classes. (For a good discussion

on this topic, see).

What that means is that the more objects you talk to, the more you run

the risk of getting broken when one of them changes. So not only do

you want to say as little as possible, you don’t want to talk to more

objects than you need to either. In fact, according to the Law of

Demeter for Methods, any method of an object should only call methods

belonging to:

  • itself.
  • any parameters that were passed in to the method.
  • any objects it created.
  • any composite objects.

Specifically missing from this list is methods belonging to objects that

were returned from some other call. For example (we’ll use Java syntax

here):

SortedList thingy = someObject.getEmployeeList(); thingy.addElementWithKey(foo.getKey(), foo);

This is what we are trying to prevent. (We also have an example of

Asking instead of Telling in foo.getKey() ).

Direct access of a child like this

extends coupling from the caller farther than it needs to be.

The caller is depending on these facts:

addElementWithKey()
getKey()

Instead, this should be:

someObject.addToThingy(foo);

Now the caller is only depending on the fact that it can add a

foo to thingy , which sounds high level

enough to have been a responsibility, not too dependent on implementation.

The disadvantage, of course, is that you end up writing many small

wrapper methods that do very little but delegate container traversal and

such.

The cost tradeoff is between that inefficiency and

higher class coupling.

The higher the degree of coupling between classes, the higher the odds

that any change you make will break something somewhere else. This

tends to create fragile, brittle code.

Depending on your application, the development and maintenance costs

of high class coupling may

easily swamp run-time inefficiencies in most cases.

Command/Query Separation

Now back to to the ask vs. tell thing. To ask is a query, to tell

is a command. I subscribe to the notion of maintaining these as

separate methods. Why bother?

  • It helps to maintain the “Tell, Don’t Ask” principle if you

    think in terms of commands that perform a very specific, well defined action.

  • It helps you to think about class invariants if your class

    is primarily command based. (If you are just tossing data out, you probably

    aren’t thinking much in the way of invariants).

  • If you can assume that evaluation of a query is free of

    any side effects, then you can:

  • use queries from within a debugger without affecting the process under test.
  • create built-in, automatic regression tests.
  • evaluate class invariants, pre- and post-conditions.

The last, of course, is why Eiffel requires only side-effect free

methods to be called from within an Assertion. But even in C++

or Java, if you want to manually check the state of an object at

some point in the code, you can do so with confidence if you know

the queries you are calling will not cause anything else to change.

References

Sharp, A. “Smalltalk By Example”

McGraw-Hill, 1997.


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

圈圈教你玩USB

圈圈教你玩USB

刘荣 / 2013-4 / 59.00元

通过U盘、USB鼠标、15SB键盘、USBMIDI键盘、USB转串口、自定义的USBHID设备和自定义的USB设备等几个具体的USB例子,一步步讲解USB设备及驱动程序和应用程序开发的详细过程和步骤。第9和10章介绍USBWDM驱动开发,并给出一个简单的USB驱动和USB上层过滤驱动的实例。第2版中新增4章内容,包括USB触摸屏设备、移植到AVR单片机和ARM微控制器上以及更多的USB设备的实现。......一起来看看 《圈圈教你玩USB》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

html转js在线工具
html转js在线工具

html转js在线工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具