首页 > 代码库 > TIJ英文原版书籍阅读之旅——Chapter One:Introduction to Objects

TIJ英文原版书籍阅读之旅——Chapter One:Introduction to Objects

 ///:~容我对这个系列美其名曰“读书笔记”,其实shi在练习英文哈:-) 

Introduction to Objects

Object-oriented programming(OOP) is part of this movement toward using the computer as an expressive medium.

This chapter will introduce you to the basic concepts of OOP, including an overview of development methods. Many people do not feel comfortable wading into OOP without understanding the big picture first. 

However, you will want to come back here eventually to fill in your knowledge so you can understand why objects are important and how to design with them.

OOP是使用计算机的一种表达媒介。初学者如果不了解全景图的话接触起来可能感觉不舒服,因此下面给出其全景图。最后,作者还风趣的说,如果你迫切的了解细节而跳过这章的内容的话,you will want to come back here eventually to fill in you knowledge so you can understand why objects are important and how to design with them.

The progress of abstraction

All programming languages provide abstractions. Assembly language is a small abstraction of underlying machine. Many so-called “imperative” languages that followed(such as FORTRAN, BASIC, and C)were abstractions of assembly language. These languages are big improvements over assembly language, but their primary abstraction still requires you to think in terms of the structure of the computer rather than the structure of the problem you are trying to solve.

所有编程语言都提供了抽象。汇编是对底层机器的较小的抽象。同样很多称作命令式语言的编程语言,FORTRAN、BASIC、C等等又是对汇编语言的抽象。然而这种初级的抽象仍然需要我们考虑机器结构而不是要解决的问题的结构。程序员必须建立介于问题空间(比如商务)和解决空间(在计算机上解决问题)之间的映射。但是它不是编程语言所固有的,生产计划难以实现并且维护代价昂贵。

The alternative to modeling the machine is to model the problem you’re trying to solve. Early languages such as LISP and APL chose particular views of the world(“All problems are ultimately lists” or “All problems are algorithmic”, respectively).

Each of these approaches may be a good solution to the particular class of problem they’re designed to solve, but when you step outside of that domain they become awkward.

所以,函数式语言LISP、APL等等由于本身局限性较大也不是一个好的选择。

The object-oriented approach goes a step further by providing tools for the programmer to represent elements in the problem space. This representation is general enough that the programmer is mot constrained to any particular type of problem.

由上面的描述我们获悉面向对象编程相比于函数式编程突破了对特殊问题的限制,问题空间的元素在解决空间里被表达为一个“Object”。(You will also need other objects that don’t hava problem-space analogs.)The idea is that the programe is allowed to adapt itself to the lingo(术语)of the problem by adding new types of objects, so when you read the code describing the solution, you’re reading words that also express the problem. This is a more flexible and powerful language abstraction than what we’ve had before. Thus, OOP allows you to describe the problem in terms of the problem, rather than in terms of the computer where the solution will run.There’s still a connection back to the computer:Each object looks quite a bit like a little computer-it has a state, and it has operations that you can ask it to perform. However, this doesn’t seem like such a bad analogy to objects in the real world-they all have characteristics and behaviors.

Alan Kay总结了5个属于Smalltalk的基本特性。Smalltalk是第一个成功的面向对象的语言,Java以其为根基。These characteristics represent a pure approach to object-oriented programming:

  1. Everything is an Object.Think of an object as a fancy variable; it stores  data, but you can “make requests” to that object, asking it to perform  operations on itself. In theory, you can take any conceptual component in  the problem you’re trying to solve(dogs, buildings, services, etc.)and  represent it as an object in your program.
  2. A program is a bunch of objects telling each other what to do by  sending messages.To make a request of an object, you “send a message”  to that object. More Concretely, you can think of a message as a request to  call a method than  belongs to a particular object.
  3. Each Object has its own memory made up of other objects. Put  another way, you create a new kind of object by making a package  containing existing objects. Thus, you can build complexity into a program  while hiding it behind the simplicity of  objects.
  4. Every object has a type.Using the parlance, each object is an instance of  a class, in  which “class” is synonymous with “type”. The most important  distinguishing characteristic of a class is “What message can you send to it?”
  5. All objects of a particular type can receive the same messages. This  is actually  a loaded statement, as you will see later. Because an object of  type “circle” is also  an object of type “shape”, a circle is guaranteed to  accept shape messages. This  means you can write code that talks to  shapes  and automatically handle anything  that fits the description of a  shape. This subsititutability is one of the powerful  concepts in OOP.

Booch offers an even more succinct description of an object:

  An Object has state, behavior and identity.

This means that an object can have internal data(which gives it state), methods(to produce behavior), and each object can be uniquely distinguished from every other object-to put this in a concrete sense, each object has a unique address in memory.

An object has an interface

One of the challenges of object-oriented programming is to create a one-to-one mapping of the object between the elements in the problem space and objects in the solution space.But how do you get an object to do useful work for you? There needs to be a way to make a request of the object so that it will do somethig, such as complete a transaction, draw something on the screen, or turn on a switch. And each object can satisfy only certain requests. The requests you can make of an object are a representation of light bulb:

 

Light lt = new Light();

lt.on();

The interface determines the requests that you can make for a particular object.

Here, the name of the type/class is Light, the name of the particular Light object is lt, and the requests that can make of a Light are to turn it on, turn it off, make it virghter, or make it dimmer. You create a Light object by defining a “reference”(lt) for that object and calling new to request a new object of that type. To send a message to the object, you state the name of the object and connect it to the message request with a period(dot).From the standpoint of the user of a predefined class, that’s pretty much all there is to programming with objects.

另外,上面的图表遵循UML(Unified Modeling Language)格式。

An object provides services

当我们尝试开发或是理解程序设计时,一个最好的方式是考虑对象作为一个“服务提供者”。作者给出了一个例子,假定我们要创建一个记账程序,我们可能考虑很多方面的内容,先前的账目,一组处理账目计算的对象,处理印刷检查的对象,打印发票的对象等等。或许一些满足上面要求的程序已经存在了(可能存在在库中),对于那些不存在的,它们应该是怎样的呢?这些尚且不存在的对象应该提供什么服务,什么对象应该能完成债务处理?If you keep doing this, you will eventually reach a point where you can say either, “That object seem simple enough to sit down and write” or “I’m sure that object much exist already”. This is a reasonable way to decompose a problem into a set of objects.并且这样做(考虑对象作为一个服务商)的一个额外的好处是:它帮助我们实现了对象之间的高内聚。In addition, in a good object-oriented design, each object does one thing well, but doesn’t try to do too much.

Treating objects an s service providers is a great simplifying tool. This is useful not only during the design process, but also when someone else is trying to understand your code or reuse an object. If they can see the value of the object based on what service it provides, it makes it much easier to fit it into the design.

The hidden implementation

为了防止客户端程序员直接操纵类库设计者创造的相关类的成员,我们需要访问控制。用访问控制的一个原因是不让客户端程序员干涉他不应当触摸到的部分,同时也让客户端程序员了解到哪些对他们来说是重要的,而那些又是可以忽略的。另一个原因是在不影响客户端程序员的情况下,类库设计者更容易修改自己的代码,比如让代码运行的更快。

Java uses three explicit keywords to set the boundaries in a class: public, private, and protected. These access specifiers determine who can use the definitions that followl. public means the following elements is avaliable to everyone. The private keyword, on the other hand, means that no one can access that elements except you, the creator of the type, inside methods of that type. private is a brick wall between you and the client programmer. Someone who tries to access a private member will get a compile-time error. The protected keyword acts like private, with the exception that an inheriting class has access to protected members, but not private members. 

Java also has a “default” access, which comes into play if you don’t use one of the aforementioned specifiers. This is usually called package access because classes can access the members of other classes in the same package(library compoment), but outside of the package those same members apprear to be private.

Reusing the implementation

The simplest way to reuse a class is to just use an object of tha class directly, but you can also place an object of that class inside a new class. We call this “creating a member object”. Your new class can be mae up of any number and type of other objects, in any combination that you need to achieve the functionality desired in your new class. Beacause you are composing a new class from exisiting classes, this concept is called composition(if the composition happens dynamically, it’s usually called aggregation). Composition is often referred to as a “has-a” relationship, as in “A car has an engine”.

 

Composition comes with a great deal of flexibility. The member objects of your new class are typically private, making them inaccessible to the client programmers who are using the class. This allows you to change those members without disturbing existing client code. You can also change the member objects at run time, to dynamically change the behavior of youprogram. Inheritance does not have the flexibility since the compiler must place compile-time restrictions on classes created with inheritance.

下面这段值得引起注意:

Because inheritance is so important in object-oriented programming, it is often highly emphasized, and the new programmer can ge the idea that inheritance should be used everywhere. This can result in awkward and overly complicated designs. Instead, you should first look to compositon when creating new classes, since it is simpler and more flexible. If you take this approach, your designs will be cleaner. Once you’ve had some experience, it will be reasonable obvious when you need inheritance.

Inheritance

By itself, the idea of an object is a convenient tool. It allows you to package data and functionality together by concept, so you can repersent an appropriate problem-space idea rather than being forced to use the idioms of the underlying machine.These concepts are expressed as fundamental units in the programming language by using the class keyword.

用UML来表示基类与派生类的关系如下:

 

Inheritance expresses this similarity between types by using the conept of base types and derived types. A base type contains all of the characteristics and behaviors that are shared among the types derived from it. You create a base type to represent the core of you ideas about some objects in you system. From the base type, you derive other types to express the different ways that this core can be realized.

拿垃圾回收器对垃圾分类来说,基类就是”垃圾”,每种垃圾都有重量,价值,能被切碎、融化、分解如此等等,而其派生类可能有其他的一些属性(瓶子有颜色)或者行为(铝罐能被压碎)。此外,一些行为可能也是不同的(纸张的价值取决于他的类型和状态)。使用继承,我们可以由问题的类型构建一个继承体系来表达和解决这个问题。

A second example is the classic “shape” example, prehaps used in a computer-aided system or game simulation. The base type is “shape”, and each shape has a size, a color, a position, and so on. Each shape can be drawn, erased, moved, colored, etc. From this, specific types of shapes are derived(inherited)-circle, square, trangle, and so on-each of which may have additional characteristics and behaviors. Certain shapes can be fipped(翻转), for example. Some behaviors may be different, such as when you want to calculate the area of a shape. The type hierarchy embodies both the similarities and differences between the shapes.

 

When you inherit from an existing type, you create a new type. This new type contains not only all the members of the existing type(although the private ones are hidden away and inaccessible——私有在物理上被继承过来了,只是逻辑上程序员不能访问它), but more importantly it duplicates the interface of the base class. 也就是说,所有能够发送给基类的消息同样能发送给派生类。This type equivalence via inheritance is one fundamental gateways in understanding the meaning of object-oriented programming.

既然基类和派生类有相同的基本接口,它们必须实现这些接口。也就是说,当一个对象接收一个特定信息的时候必须有相应的代码得到执行。如过我们简单的继承一个类而不做额外的事情,那么基类接口中的方法会直接搬入到派生类。同基类对象相比,这意味着派生类对象不仅与之有相同的接口,而且与之有相同的行为,这并不怎么有趣。

有两种方式可以让我们的派生类与基类区分开来。一种方式如下:

它是简单直白的,通过给派生类增加新的方法,例如对三角形类增添水平翻转和垂直翻转方法。这是用继承的一种简单原始的方法,有时它能完美的解决你的问题。However, you should look closely for the possiblity that your base class might also need these additional methods. This process of discovery and iteration of your design happens regularly in OOP.

Although inheritance may sometimes imply(especially in Java, where the keyword for inheritance is extends)than you are going to add new methods to the interface, that’s not necessarily true. The second and more important way to differentiate your new class is to change the behavior of an existing base-class method. This is referred to as overriding that method. 

To override a method, you simply create a new definition for the method in the derived class. You’re saying, “I’m using the same interface method here, but I want it to do something different for my new type”.

   |_Is-a vs. is-like-a relationships

如果派生类只是重写了父类的方法,而没有增添额外的方法,即派生类与父类有完全一样的接口,这被认为是纯粹置换(pure substitution),也被称为置换原则(substitution principle),because you can say, “A circle is a shape.”

当为派生类扩展接口的时候,替代关系不是完美的,因为从基类中不能访问新增的方法,以作者的术语来说,这是is-like-a的关系。

Interchangeable objects with polymorphism

多态允许我们不必依赖特定的类型来写代码。以前面讲述过的“形状”为例,方法操纵一般的图形而不用考虑它们是圆形、正方形、三角形还是其他的一些从未定义过的形状。所有图形能被画、擦除、移动(drawn, erased, and moved),这些方法简单的发送信息给一个shape object,它们不用考虑这个对象如何处理该信息。

这样的代码不受新增类型的影响,而且能很好的适应新增类型。This ability to easily extend a design by deriving new subtypes is one of the essential ways to encapsulate change. This greatly improves designs while reducing the cost of software maintenance.

问题是当我们把子类对象看作父类对象,并且给它发送信息的时候,在编译期间编译器不能精确的获悉哪段代码将要执行。然而最终正确的事情发生了,这是为什么呢?

The answer is the primary twist in object-oriented programming: The compiler cannot make a function call in the traditional sense. The function call generated by a non-OOP compiler causes what is called early binding(早绑定). It means the compiler generates a call to a specific function name, and the runtime system resolves this call to the absolute address of the code to be executed. In OOP, the program cannot determine the address of the code until run time, so some other scheme is necessary when a message is sent to a generic object.

To solve the problem, object-oriented languages use the concept of late bingding(晚绑定). When you send a message to an object, the code being called isn’t determined until run time. The compiler does ensure that the method exists and performs type checking on the arguments and return value, but it doesn’t know the exact code to execute.

To perform late binding, Java uses a special bit of code in lieu of the absolute call. This code calculates the address of the method body, using information stored in the object(细节会在多态那章讲述). Thus, each object can behave differently according to the contents of the special bit of code. When you send a message to an object, the object actually dose figure out what to do with that message.

C++中默认是没有动态绑定(同晚绑定)的,必须用“virtual”关键字标识才能使用。In Java, dynamic binding is the default behavior and you don’t need to remember to add any extra keywords in order to get polymorphism.

 

示范多态的例子:

如果我们写下这样的方法:

void doSomething(Shape shape){

shpae.erase();

// ...

shape.draw();

}

假定程序的其他地方使用了doSomething()方法:

Circle circle = new Circle();

Triangle triangle = new Triangle();

Line line = new Line();

doSomething(circle);

doSomething(triangle);

doSomething(line);

 

The calls to doSomething() automatically work correctly, regardless of the exact type of the object.

We call this process of treating a derived type as thought it were its base type upcasting(向上转型).

An object-oriented program contains some upcasting somewhere, because that’s how you decouple yourself from knowing about the exact type you’re working with. 看方法doSomething()中的代码:

shape.erase();

// ...

shape.draw();

注意到它并没有说:“如果是圆形,做这个,是正方形做那个等等”,如果你写下了这种代码——检查一个图形真正的类型,然后做相应的事情。它将是混乱的而且每次添加一个图形类都需要修改代码。Here, you just say, “You’re a shape, I know you can erase() and draw() yourself, do it, and take care of the details correctly”.

令人印象深刻的是在doSomething()中的代码不知怎的就奇迹般的做了正确的事情。调用圆的draw()方法和调用正方形或直线的该方法会导致不同的代码得到执行,but when the draw() message is sent to an anonymous Shape, the correct behavior occurs based on the actual type of the Shape. This is amazing beacause, as mentioned earlier, when the Java compiler is compiling the code for doSomething(), it cannot know exactly what types it is dealing with. So ordinarily, you’d expect it to end up calling the version of earse() and draw() for the base class Shape, and not for the sepcific Circle, Square, or Line. And yet the right thing happens because of polymorphism. The compiler and runtime system handle the details; all you need to know right now is that it does happen, and more importantly, how to dedign with it. When you send a message to an object, the object will do the right thing, even when upcasting is involved.

The single rooted hierarchy 

没什么可说的,Java支持单根继承,所有的类都直接或间接地继承自Object。C++中没有这个特性。补充一句:结果证明单根继承是有很多好处的,它保证了任何一个类都有某些相同的基本功能,此外单根继承更容易实现GC(garbage collector,垃圾回收器)。

Containers

有时,有一些特别的需求需要用到容器(List、Map、Set),利用容器可以存储各种对象的引用。而且有时候我们需要对用哪种容器做出选择,比如ArrayList便于检索,而LinkedList在插入删除数据频繁时表现得更好一些。这种效率的区分是由于它们的底层结构不同造成的(ArrayList底层由数组实现,而LinkedList底层由双向不循环链表实现)。

  |_Parameterized types (generics)

在Java SE5以前,容器中持有通用类型:Object。由Java的单根继承我们知道万物皆Object。所以持有Object意味着可以持有一切(Java SE5的自动装箱技术突破了原生数据类型不能放入容器的限制)。

然而,这导致了一些问题。因为取出的时候需要向下类型转换(downcast),这就需要我们精确的记住存入的引用类型。向下类型转换和运行期检查(向下类型转换错误会抛出异常)无疑会延长程序的运行时间,而且也需要程序员付出额外的努力(显式downcast)。为了消除向下转型和可能出现的错误,我们使用泛型,使得编译器能够根据给定的类来自动定制,比如它只接收Shape对象的引用并且取出来也只是Shape类对象的引用。简短示例:

ArrayList<Shape> shapes = new ArrayList<Shape>();

PS一点个人感悟:人类造工具的特点就是先造出工具够用就好,然后在使用的过程中不断的发现它的一些缺点,然后改进它,一个人成长的过程也大抵如此吧:-)

Object creation & lifetime

Java采用动态内存分配,对象被放置在堆上。垃圾回收器“知道”什么时候对象不再被使用并且自动回收它占据的内存空间。

Exception handling: dealing with errors

Java中的异常分为两种,运行时异常和非运行时异常。非运行时异常必须被处理,而运行时异常可处理可不处理。对于非运行时异常的处理方式有两种:第一种是使用try catch finally对其进行捕获;第二种是在产生异常的方法声明处throws。

值得注意的是异常处理不是面向对象的一个特性,即使在面向对象的语言中异常通常被一个对象代表。异常处理出现在面向对象语言之前。

Concurrent programming

对于单核处理器可以做到宏观并行、微观串行,各个任务分摊时间。而对于多核处理器就可以做到微观并行,每个任务被分配到不同的处理器上,同时运行。

应当注意的是,多线程同时访问相同的资源时容易造成线程死锁,怎么处理这种情况呢?

看看作者的论述:

All this makes concurrency sound pretty simple. Theres is a catch: shared resources. If you hava more than one task running that’s expecting to access the same resource, you have a problem. For example, two processes can’t simultaneously send information to a printer. To solve the problem, resources that can be share, such as the printer, must be locked while they are being used. So a task locks a resource, completes its task, and then releases the lock so that someone else can use the resource.

Java and the Internet

作者主要介绍了什么是Web,客户端与服务器端的概念,客户端编程,服务器端编程,脚本语言,.NET平台与C#语言和JVM与Java的关系。

Summary

直接全部札记:

You know what a procedural program looks like: data definitions and function calls. To find the meaning of such a program, you must work at it, looking through the function calls and low-level concepts to create a model in your mind. This is the reason we need intermediate representations when designing procedural programs-by themselves, these programes tend to be confusing because the terms of expressing are oriented more toward the computer than to the problem you’re solving.

Because OOP adds many new concepts on top of what you find in a procedural language, your natural assumption may be that the resulting Java program will be far more complicates than the equivalent procedural program. Here, you’ll be pleasantly surprised: A well-written Java program is generally far simpler and much easier to understand than a procedural program. What you’ll see are the definitions of the objects that represent concepts in your problem space(rather than the issues of the computer representation) and messages sent to those objects to represent the activites in that space. One of the delights of objcet-oriented programming is that, with a well-designed program, it’s easy to understand the code by reading it. Ususlly, there’s a lot less code as well, because many of your problems will be solved by reusing existing library code.

OOP and Java may not be for everyone. It’s important to evaluate your own needs and decide whether Java will optimally satisfy those needs, or if you might be better off with another programming system(including the one you’re currently using). If you know that your needs will be very specialized for the foreseeable futrue and if you have specific constraints that may not be satisfied by Java, then you owe it to yourself to investigate the alternatives(in particular, I recommend looking at Python; see www.Python.org). If you still choose Java as your language, you’ll at least understand what the options were and have a clear vision of why you took that direction.

 

最后:

一些生词&短语:

representation:表示

restrict:限制

analogy:类比

conceptual:概念上的

parlance:用语

synonymous:同义的

substitutability:可替换性

succinct:简洁的

transaction:事务

particular:特别的,typical:通常的,典型的

specifier:[计]说明符,specify:指定

especially:特别是

period:句点;周期

standpoint:立场;观点

provides:提供

decompose:分解

inheritance:继承

composition:组合

aggregation:聚合

disturb:干扰

restriction:限制

emphasize:强调

awkward:尴尬的,棘手的

complicated:复杂的,难懂的

flexible:灵活的

derived:派生的

simulation:模拟

embody:体现

duplicate:复制

equivalence:等价性

regularly:经常,有规律的

imply:暗示,隐含

interchangeable:可交换的

polymorphism:多态性

scheme:方案

decouple:去耦

anonymous:匿名的

intermediate:中间的

evaluate:评估

optimally:最佳;最适宜

investigate:调查;研究

recommend:推荐

 

in terms of:就...而言

a bunch of:一串

pretty much:几乎

brick wall:砖墙

come into play:发挥作用

come with:伴随

look to:注意,留心

by itself:单独的

computer-aided design system:计算机辅助设计系统

type hierarchy:类型层次结构

in lieu of:代替

TIJ英文原版书籍阅读之旅——Chapter One:Introduction to Objects