`
leonzhx
  • 浏览: 793589 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Chapter 1. Introduction to Objects -- Thinking in Java

阅读更多

1)  All programming languages provide abstractions. It can be argued that the complexity of the problems you’re able to solve is directly related to the kind and quality of abstraction. By “kind” I mean, “What is it that you are abstracting?”

 

2) The programmer must establish the association between the machine model (in the “solution space,” which is the place where you’re implementing that solution, such as a computer) and the model of the problem that is actually being solved (in the “problem space,” which is the place where the problem exists, such as a business). The effort required to perform this mapping, and the fact that it is extrinsic to the programming language, produces programs that are difficult to write and expensive to maintain, and as a side effect created the entire “programming methods” industry.

 

3) 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 not constrained to any particular type of problem. We refer to the elements in the problem space and their representations in the solution space as “objects.” The idea is that the program 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.

 

4) The following characteristics represent a pure approach to object-oriented programming:

    a. Everything is an object.

    b. A program is a bunch of objects telling each other what to do by sending messages.

    c. Each object has its own memory made up of other objects.

    d. Every object has a type.

    e. All objects of a particular type can receive the same messages.

 

5) What we really do in object-oriented programming is create new data types. And since a class describes a set of objects that have identical characteristics (data elements) and behaviors (functionality), a class is really a data type. A programmer defines a class to fit a problem rather than being forced to use an existing data type that was designed to represent a unit of storage in a machine.

 

6) Indeed, one of the challenges of object-oriented programming is to create a one-to-one mapping between the elements in the problem space and objects in the solution space.

 

7) Each object can satisfy only certain requests. The requests you can make of an object are defined by its interface, and the type is what determines the interface.The interface determines the requests that you can make for a particular object. However, there must be code somewhere to satisfy that request. This, along with the hidden data, comprises the implementation. A type has a method associated with each possible request, and when you make a particular request to an object, that method is called. This process is usually summarized by saying that you “send a message” (make a request) to an object, and the object figures out what to do with that message (it executes code).

 

8) While you’re trying to develop or understand a program design, one of the best ways to think about objects is as “service providers.” Your program itself will provide services to the user, and it will accomplish this by using the services offered by other objects. Your goal is to produce (or even better, locate in existing code libraries) a set of objects that provide the ideal services to solve your problem.

 

9) Maybe some of the objects you need to resolve the problem already exist, and for the ones that don’t, what would they look like? What services would those objects provide, and what objects would they need to fulfill their obligations? If you keep doing this, you will eventually reach a point where you can say either, “That object seems simple enough to sit down and write” or “I’m sure that object must exist already.” This is a reasonable way to decompose a problem into a set of objects.

 

10) The goal of the client programmer is to collect a toolbox full of classes to use for rapid application development. The goal of the class creator is to build a class that exposes only what’s necessary to the client programmer and keeps everything else hidden. Why? Because if it’s hidden, the client programmer can’t access it, which means that the class creator can change the hidden portion at will without worrying about the impact on anyone else. The hidden portion usually represents the tender insides of an object that could easily be corrupted by a careless or uninformed client programmer, so hiding the implementation reduces program bugs.

 

11) The first reason for access control is to keep client programmers’ hands off portions they shouldn’t touch—parts that are necessary for the internal operation of the data type but not part of the interface that users need in order to solve their particular problems. This is actually a service to client programmers because they can easily see what’s important to them and what they can ignore.

 

12) The second reason for access control is to allow the library designer to change the internal workings of the class without worrying about how it will affect the client programmer.

 

13) 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 follow. public means the following element is available to everyone. The private keyword, on the other hand, means that no one can access that element 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 component), but outside of the package those same members appear to be private.

 

The access behavior is class level ( method that access the field is of a class instead of an instance). To determine whether a method can access a field or a method of other class:

   a) if the accessed field or method is private, only the method in the same class can access it.

   b) if the accessed field or method is protected, only the method in the same class or subclass can access it. But you cannot access it in other subclass (see the following example)

   c) if the accessed field or method is package, then either the method that can access it if it's protected, or is in the same package of the owner class can access it.

 

Example, if class B and C extend class A, and b1, b2 are the instance of class B and c , a are the instance of class C and A respectively. Then b1 can access b2's private (defined in B) and protected members ( either defined in B or A)。But b1 cannot access protected members (defined in A) of c and a. But a can access the protected member of b and c defined in A.

 

14) Composing a new class from existing classes is called composition (if the composition happens dynamically, it’s usually called aggregation). Composition is often referred to as a “has-a” relationship. The member objects of your new class are typically private, which 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 your program. Inheritance does not have this flexibility since the compiler must place compile-time restrictions on classes created with inheritance. You should first look to composition when creating new classes, since it is simpler and more flexible. If you take this approach, your designs will be cleaner.

 

15) Inheritance expresses the similarity between types by using the concept 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 your ideas about some objects in your system. From the base type, you derive other types to express the different ways that this core can be realized.

 

16) 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. That is, all the messages you can send to objects of the base class you can also send to objects of the derived class. Since we know the type of a class by the messages we can send to it, this means that the derived class is the same type as the base class.

 

17) You have two ways to differentiate your new derived class from the original base class. The first is quite straightforward: You simply add brand new methods to the derived class. These new methods are not part of the base-class interface. This means that the base class simply didn’t do as much as you wanted it to, so you added more methods. 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.”

 

18) A test for inheritance is to determine whether you can state the is-a relationship about the classes and have it make sense. When you see the substitution principle it’s easy to feel like pure substitution is the only way to do things, and in fact it is nice if your design works out that way. But you’ll find that there are times when it’s equally clear that you must add new methods to the interface of a derived class.

 

19) 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 binding. 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 that special bit of code. In some languages you must explicitly state that you want a method to have the flexibility of latebinding properties (C++ uses the virtual keyword to do this). In these languages, by default, methods are not dynamically bound. 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.

 

20) We call this process of treating a derived type as though it were its base type upcasting.

 

21) In Java, all classes should ultimately be inherited from a single base class and the name of this ultimate base class is simply Object . All objects in a singly rooted hierarchy can be guaranteed to have certain functionality. All objects can easily be created on the heap, and argument passing is greatly simplified.A singly rooted hierarchy makes it much easier to implement a garbage collector, which is one of the fundamental improvements of Java over C++.

 

22) Before Java SE5, containers held the one universal type in Java: Object . The singly rooted hierarchy means that everything is an Object , so a container that holds Objects can hold anything.This made containers easy to reuse. To use such a container, you simply add object references to it and later ask for them back. But, since the container held only Objects , when you added an object reference into the container it was upcast to Object , thus losing its character. When fetching it back, you got an Object reference, and not a reference to the type that you put in. Here, the cast is used again, you cast down the hierarchy to a more specific type. This manner of casting is called downcasting. With upcasting, you know, for example, that a Circle is a type of Shape so it’s safe to upcast, but you don’t know that an Object is necessarily a Circle or a Shape so it’s hardly safe to downcast unless you know exactly what you’re dealing with.

 

23) Downcasting and the runtime checks require extra time for the running program and extra effort from the programmer. Wouldn’t it make sense to somehow create the container so that it knows the types that it holds, eliminating the need for the downcast and a possible mistake? The solution is called a parameterized type mechanism. A parameterized type is a class that the compiler can automatically customize to work with particular types. One of the big changes in Java SE5 is the addition of parameterized types, called generics in Java. You’ll recognize the use of generics by the angle brackets with types inside.

 

24) Where is the data for an object and how is the lifetime of the object controlled? For maximum runtime speed, the storage and lifetime can be determined while the program is being written, by placing the objects on the stack (these are sometimes called automatic or scoped variables) or in the static storage area. This places a priority on the speed of storage allocation and release, and this control can be very valuable in some situations. However, you sacrifice flexibility because you must know the exact quantity, lifetime, and type of objects while you’re writing the program. The second approach is to create objects dynamically in a pool of memory called the heap. In this approach, you don’t know until run time how many objects you need, what their lifetime is, or what their exact type is. Those are determined at the spur of the moment while the program is running.

 

25) Because the storage is managed dynamically, at run time, the amount of time required to allocate storage on the heap can be noticeably longer than the time to create storage on the stack. Creating storage on the stack is often a single assembly instruction to move the stack pointer down and another to move it back up. The time to create heap storage depends on the design of the storage mechanism.

 

26) The dynamic approach makes the generally logical assumption that objects tend to be complicated, so the extra overhead of finding storage and releasing that storage will not have an important impact on the creation of an object. In addition, the greater flexibility is essential to solve the general programming problem. Java uses dynamic memory allocation, exclusively. Every time you want to create an object, you use the new operator to build a dynamic instance of that object.

 

27) With languages that allow objects to be created on the stack, the compiler determines how long the object lasts and can automatically destroy it. However, if you create it on the heap the compiler has no knowledge of its lifetime. Java provides a feature called a garbage collector that automatically discovers when an object is no longer in use and destroys it. A garbage collector is much more convenient because it reduces the number of issues that you must track and the code you must write. More importantly, the garbage collector provides a much higher level of insurance against the insidious problem of memory leaks.

 

28) Exception handling wires error handling directly into the programming language and sometimes even the operating system. An exception is an object that is “thrown” from the site of the error and can be “caught” by an appropriate exception handler designed to handle that particular type of error. It’s as if exception handling is a different, parallel path of execution that can be taken when things go wrong. And because it uses a separate execution path, it doesn’t need to interfere with your normally executing code. This tends to make that code simpler to write because you aren’t constantly forced to check for errors. In addition, a thrown exception is unlike an error value that’s returned from a method or a flag that’s set by a method in order to indicate an error condition—these can be ignored. An exception cannot be ignored, so it’s guaranteed to be dealt with at some point. Finally, exceptions provide a way to reliably recover from a bad situation. Instead of just exiting the program, you are often able to set things right and restore execution, which produces much more robust programs. Java’s exception handling stands out among programming languages, because in Java, exception handling was wired in from the beginning and you’re forced to use it. It is the single acceptable way to report errors. If you don’t write your code to properly handle exceptions, you’ll get a compile-time error message. This guaranteed consistency can sometimes make error handling much easier.

 

29) Sometimes, interrupts are necessary for handling time-critical tasks, but there’s a large class of problems in which you’re simply trying to partition the problem into separately running pieces (tasks) so that the whole program can be more responsive. Within a program, these separately running pieces are called threads, and the general concept is called concurrency.

 

30) The value of the plug-in for client-side programming is that it allows an expert programmer to develop extensions and add those extensions to a browser without the permission of the browser manufacturer. Thus, plug-ins provide a “back door” that allows the creation of new client-side programming languages (although not all languages are implemented as plug-ins).

 

31) Java allows client-side programming via the applet and with Java Web Start. An applet is a mini-program that will run only under a Web browser. The applet is downloaded automatically as part of a Web page. When the applet is activated, it executes a program. This is part of its beauty—it provides you with a way to automatically distribute the client software from the server at the time the user needs the client software, and no sooner.

 

32) The .NET platform is roughly the same as the Java Virtual Machine (JVM; the software platform on which Java programs execute) and Java libraries, and C# bears unmistakable similarities to Java.

 

33) What you’ll see in OOP 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 activities in that space. One of the delights of object-oriented programming is that, with a well-designed program, it’s easy to understand the code by reading it. Usually, there’s a lot less code as well, because many of your problems will be solved by reusing existing library code.

 

 

Late binding is only for those non-static method :

 

 

package basic;

import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class TestEarlyBinding {
	
	
	
	@Test
	public void testEarlyBinding(){
		SubType sub = new SubType();
		SuperType sup = sub;
		assertEquals(0, sup.earlyBinding());
		assertEquals(1, sub.earlyBinding());
	}
	

	

}

class SuperType {
	public static int earlyBinding(){
		return 0;
	}
}

class SubType extends SuperType {
	public static int earlyBinding() {
		return 1;
	}
}

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics