1. Trang chủ
  2. » Công Nghệ Thông Tin

Data Structures and Algorithms in Java 4th phần 2 pptx

92 438 1

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 92
Dung lượng 1,9 MB

Nội dung

Modularity In addition to abstraction and encapsulation, a fundamental principle of object oriented design is modularity. Modern software systems typically consist of several different components that must interact correctly in order for the entire system to work properly. Keeping these interactions straight requires that these different components be well organized. In object-oriented design, this code structuring approach centers around the concept of modularity. Modularity refers to an organizing principle for code in which different components of a software system are divided into separate functional units. Hierarchical Organization The structure imposed by modularity helps to enable software reusability. If software modules are written in an abstract way to solve general problems, then modules can be reused when instances of these same general problems arise in other contexts. For example, the structural definition of a wall is the same from house to house, typically being defined in terms of 2- by 4-inch studs, spaced a certain distance apart, etc. Thus, an organized architect can reuse his or her wall definitions from one house to another. In reusing such a definition, some parts may require redefinition, for example, a wall in a commercial building may be similar to that of a house, but the electrical system and stud material might be different. A natural way to organize various structural components of a software package is in a hierarchical fashion, which groups similar abstract definitions together in a level-by-level manner that goes from specific to more general as one traverses up the hierarchy. A common use of such hierarchies is in an organizational chart, where each link going up can be read as "is a," as in "a ranch is a house is a building." This kind of hierarchy is useful in software design, for it groups together common functionality at the most general level, and views specialized behavior as an extension of the general one. Figure 2.3: An example of an "is a" hierarchy involving architectural buildings. 94 2.1.3 Design Patterns One of the advantages of object-oriented design is that it facilitates reusable, robust, and adaptable software. Designing good code takes more than simply understanding object-oriented methodologies, however. It requires the effective use of object- oriented design techniques. Computing researchers and practitioners have developed a variety of organizational concepts and methodologies for designing quality object-oriented software that is concise, correct, and reusable. Of special relevance to this book is the concept of a design pattern, which describes a solution to a "typical" software design problem. A pattern provides a general template for a solution that can be applied in many different situations. It describes the main elements of a solution in an abstract way that can be specialized for a specific problem at hand. It consists of a name, which identifies the pattern, a context, which describes the scenarios for which this pattern can be applied, a template, which describes how the pattern is applied, and a result, which describes and analyzes what the pattern produces. We present several design patterns in this book, and we show how they can be consistently applied to implementations of data structures and algorithms. These design patterns fall into two groups—patterns for solving algorithm design problems and patterns for solving software engineering problems. Some of the algorithm design patterns we discuss include the following: • Recursion (Section 3.5) • Amortization (Section 6.1.4 ) 95 • Divide-and-conquer (Section 11.1.1) • Prune-and-search, also known as decrease-and-conquer (Section 11.7.1) • Brute force (Section 12.2.1) • The greedy method (Section 12.4.2 ) • Dynamic programming (Section 12.5.2 ). Likewise, some of the software engineering design patterns we discuss include: • Position (Section 6.2.2) • Adapter (Section 6.1.2) • Iterator (Section 6.3) • Template method (Sections 7.3.7, 11.6, and 13.3.2) • Composition (Section 8.1.2) • Comparator (Section 8.1.2) • Decorator (Section 13.3.1). Rather than explain each of these concepts here, however, we introduce them throughout the text as noted above. For each pattern, be it for algorithm engineering or software engineering, we explain its general use and we illustrate it with at least one concrete example. 2.2 Inheritance and Polymorphism To take advantage of hierarchical relationships, which are common in software projects, the object-oriented design approach provides ways of reusing code. 2.2.1 Inheritance The object-oriented paradigm provides a modular and hierarchical organizing structure for reusing code, through a technique called inheritance. This technique allows the design of general classes that can be specialized to more particular classes, with the specialized classes reusing the code from the general class. The general class, which is also known as a base class or superclass, can define standard instance variables and methods that apply in a multitude of situations. A class that specializes, or extends, or inherits from, a superclass need not give new implementations for the general methods, for it inherits them. It should only define those methods that are specialized for this particular subclass. 96 Example 2.1: Consider a class S that defines objects with a field, x, and three methods, a(), b(), and c(). Suppose we were to define a classT that extendsS and includes an additional field, y, and two methods, d() ande(). The classT would theninherit the instance variablex and the methodsa(), b(), andc() fromS. We illustrate the relationships between the classS and the classT in aclass inheritance diagram in Figure 2.4. Each box in such a diagram denotes a class, with its name, fields (or instance variables), and methods included as subrectangles. Figure 2.4: A class inheritance diagram. Each box denotes a class, with its name, fields, and methods, and an arrow between boxes denotes an inheritance relation. Object Creation and Referencing When an object o is created, memory is allocated for its data fields, and these same fields are initialized to specific beginning values. Typically, one associates the new object o with a variable, which serves as a "link" to object o, and is said to reference o. When we wish to access object o (for the purpose of getting at its fields or executing its methods), we can either request the execution of one of o's methods (defined by the class that o belongs to), or look up one of the fields of o. Indeed, the primary way that an object p interacts with another object o is for p to 97 send a "message" to o that invokes one of o's methods, for example, for o to print a description of itself, for o to convert itself to a string, or for o to return the value of one of its data fields. The secondary way that p can interact with o is for p to access one of o's fields directly, but only if o has given other objects like p permission to do so. For example, an instance of the Java class Integer stores, as an instance variable, an integer, and it provides several operations for accessing this data, including methods for converting it into other number types, for converting it to a string of digits, and for converting strings of digits to a number. It does not allow for direct access of its instance variable, however, for such details are hidden. Dynamic Dispatch When a program wishes to invoke a certain method a() of some object o, it sends a message to o, which is usually denoted, using the dot-operator syntax (Section 1.3.2), as "o.a()." In the compiled version of this program, the code corresponding to this invocation directs the run-time environment to examine o's class T to determine if the class T supports an a() method, and, if so, to execute it. Specifically, the run-time environment examines the class T to see if it defines an a() method itself. If it does, then this method is executed. If T does not define an a() method, then the run-time environment examines the superclass S of T. If S defines a(), then this method is executed. If S does not define a(), on the other hand, then the run-time environment repeats the search at the superclass of S. This search continues up the hierarchy of classes until it either finds an a() method, which is then executed, or it reaches a topmost class (for example, the Object class in Java) without an a() method, which generates a run-time error. The algorithm that processes the message o.a() to find the specific method to invoke is called the dynamic dispatch (or dynamic binding) algorithm, which provides an effective mechanism for locating reused software. It also allows for another powerful technique of object-oriented programming—polymorphism. 2.2.2 Polymorphism Literally, "polymorphism" means "many forms." In the context of object-oriented design, it refers to the ability of an object variable to take different forms. Object- oriented languages, such as Java, address objects using reference variables. The reference variable o must define which class of objects it is allowed to refer to, in terms of some class S. But this implies that o can also refer to any object belonging to a class T that extends S. Now consider what happens if S defines an a() method and T also defines an a() method. The dynamic dispatch algorithm for method invocation always starts its search from the most restrictive class that applies. When o refers to an object from class T, then it will use T's a() method when asked for o.a(), not S's. In this case, T is said to override method a() from S. Alternatively, when o refers to an object from class S (that is not also a T object), it will execute 98 S's a() method when asked for o.a(). Polymorphism such as this is useful because the caller of o.a() does not have to know whether the object o refers to an instance of T or S in order to get the a() method to execute correctly. Thus, the object variable o can be polymorphic, or take many forms, depending on the specific class of the objects it is referring to. This kind of functionality allows a specialized class T to extend a class S, inherit the standard methods from S, and redefine other methods from S to account for specific properties of objects of T. Some object-oriented languages, such as Java, also provide a useful technique related to polymorphism, which is called method overloading. Overloading occurs when a single class T has multiple methods with the same name, provided each one has a different signature. The signature of a method is a combination of its name and the type and number of arguments that are passed to it. Thus, even though multiple methods in a class can have the same name, they can be distinguished by a compiler, provided they have different signatures, that is, are different in actuality. In languages that allow for method overloading, the run-time environment determines which actual method to invoke for a specific method call by searching up the class hierarchy to find the first method with a signature matching the method being invoked. For example, suppose a class T, which defines a method a(), extends a class U, which defines a method a(x,y). If an object o from class T receives the message "o.a(x,y)," then it is U's version of method a that is invoked (with the two parameters x and y). Thus, true polymorphism applies only to methods that have the same signature, but are defined in different classes. Inheritance, polymorphism, and method overloading support the development of reusable software. We can define classes that inherit the standard instance variables and methods and can then define new more-specific instance variables and methods that deal with special aspects of objects of the new class. 2.2.3 Using Inheritance in Java There are two primary ways of using inheritance of classes in Java, specialization and extension. Specialization In using specialization we are specializing a general class to particular subclasses. Such subclasses typically possess an "is a" relationship to their superclass. A subclass then inherits all the methods of the superclass. For each inherited method, if that method operates correctly independent of whether it is operating for a specialization, no additional work is needed. If, on the other hand, a general method of the superclass would not work correctly on the subclass, then we should override the method to have the correct functionality for the subclass. For example, we could have a general class, Dog, which has a method drink and a method sniff. Specializing this class to a Bloodhound class would probably not 99 require that we override the drink method, as all dogs drink pretty much the same way. But it could require that we override the sniff method, as a Bloodhound has a much more sensitive sense of smell than a standard dog. In this way, the Bloodhound class specializes the methods of its superclass, Dog. Extension In using extension, on the other hand, we utilize inheritance to reuse the code written for methods of the superclass, but we then add new methods that are not present in the superclass, so as to extend its functionality. For example, returning to our Dog class, we might wish to create a subclass, BorderCollie, which inherits all the standard methods of the Dog class, but then adds a new method, herd, since Border Collies have a herding instinct that is not present in standard dogs. By adding the new method, we are extending the functionality of a standard dog. In Java, each class can extend exactly one other class. Even if a class definition makes no explicit use of the extends clause, it still inherits from exactly one other class, which in this case is class java.lang.Object. Because of this property, Java is said to allow only for single inheritance among classes. Types of Method Overriding Inside the declaration of a new class, Java uses two kinds of method overriding, refinement and replacement. In the replacement type of overriding, a method completely replaces the method of the superclass that it is overriding (as in the sniff method of Bloodhound mentioned above). In Java, all regular methods of a class utilize this type of overriding behavior. In the refinement type of overriding, however, a method does not replace the method of its superclass, but instead adds additional code to that of its superclass. In Java, all constructors utilize the refinement type of overriding, a scheme called constructor chaining. Namely, a constructor begins its execution by calling a constructor of the superclass. This call can be made explicitly or implicitly. To call a constructor of the superclass explicitly, we use the keyword super to refer to the superclass. (For example, super() calls the constructor of the superclass with no arguments.) If no explicit call is made in the body of a constructor, however, the compiler automatically inserts, as the first line of the constructor, a call to super(). (There is an exception to this general rule, which is discussed in the next section.) Summarizing, in Java, constructors use the refinement type of method overriding whereas regular methods use replacement. The Keyword this 100 Sometimes, in a Java class, it is convenient to reference the current instance of that class. Java provides a keyword, called this, for such a reference. Reference this is useful, for example, if we would like to pass the current object as a parameter to some method. Another application of this is to reference a field inside the current object that has a name clash with a variable defined in the current block, as shown in the program given in Code Fragment 2.1. Code Fragment 2.1: Sample program illustrating the use of reference this to disambiguate between a field of the current object and a local variable with the same name. When this program is executed, it prints the following: The dog local variable =5.0 The dog field = 2 An Illustration of Inheritance in Java To make some of the notions above about inheritance and polymorphism more concrete, let us consider some simple examples in Java. In particular, we consider a series of several classes for stepping through and printing out numeric progressions. A numeric progression is a sequence of numbers, where each number depends on one or more of the previous numbers. For example, an arithmetic progression determines the next number by addition and a geometric progression determines the next number by multiplication. In 101 any case, a progression requires a way of defining its first value and it needs a way of identifying the current value as well. We begin by defining a class, Progression, shown in Code Fragment 2.2 , which defines the standard fields and methods of a numeric progression. Specifically, it defines the following two long-integer fields: • first: first value of the progression; • cur: current value of the progression; and the following three methods: firstValue(): Reset the progression to the first value, and return that value. nextValue(): Step the progression to the next value and return that value. printProgression(n): Reset the progression and print the first n values of the progression. We say that the method printProgression has no output in the sense that it does not return any value, whereas the methods firstValue and nextValue both return long-integer values. That is, firstValue and nextValue are functions, and printProgression is a procedure. The Progression class also includes a method Progression(), which is a constructor. Recall that constructors set up all the instance variables at the time an object of this class is created. The Progression class is meant to be a general superclass from which specialized classes inherit, so this constructor is code that will be included in the constructors for each class that extends the Progression class. Code Fragment 2.2: General numeric progression class. 102 103 [...]... testing by the Java run-time environment during program execution Narrowing Conversions A narrowing conversion occurs when a type T is converted into a "narrower" type S The following are common cases of narrowing conversions: 122 • T and S are class types and S is a subclass of T • T and S are interface types and S is a subinterface of T • T is an interface implemented by class S In general, a narrowing... we present in Code Fragment 2. 3 This class defines an arithmetic progression, where the next value is determined by adding a fixed increment, inc, to the previous value ArithProgression inherits fields first and cur and methods firstValue() and printProgression(n) from the Progression class It adds a new field, inc, to store the increment, and two constructors for setting the increment Finally, it... specified in the interface.This requirement is known as strong typing Having to define interfaces and then having those definitions enforced by strong typing admittedly places a burden on the programmer, but this burden is offset by the rewards it provides, for it enforces the encapsulation principle and often catches programming errors that would otherwise go unnoticed 2. 4.1 Implementing Interfaces The main... ClassCastException from being thrown in the code fragment above by modifying it as follows: Number n; Integer i; n = new Integer(3); if (n instanceof Integer) i = (Integer) n; // This is legal n = new Double(3.1415); if (n instanceof Integer) i = (Integer) n; // This will not be attempted Casting with Interfaces Interfaces allow us to enforce that objects implement certain methods, but using interface variables... findOther to type Student works fine as long as we are sure that the call to myDirectory.findOther is really giving us a Student object In general, interfaces can be a valuable tool for the design of general data structures, which can then be specialized by other programmers through the use of casting 2. 5 .2 Generics 126 Starting with 5.0, Java includes a generics framework for using abstract types in. .. class Namely, the Java number classes (shown in Table 1 .2) specialize an abstract class called java. lang.Number Each concrete number class, such as java. lang.Integer and java. lang.Double, extends the java. lang.Number class and fills in the details for the abstract methods of the superclass In particular, the methods intValue, floatValue, doubleValue, and longValue are all abstract in java. lang.Number... application programming interface (API), or simply interface, that their objects present to other objects In the ADT-based approach (see Section 2. 1 .2) to data structures followed in this book, an interface defining an ADT is specified as a type definition and a collection of methods for this type, with the arguments for each method being of specified types This specification is, in turn, enforced by... a class in Java can extend only one other class, it can nevertheless implement many interfaces 2. 4 .2 Multiple Inheritance in Interfaces 119 The ability of extending from more than one class is known as multiple inheritance In Java, multiple inheritance is allowed for interfaces but not for classes The reason for this rule is that the methods of an interface never have bodies, while methods in a class... Returning to our example of the antique objects, we could define an interface for insurable items as follows: public interface InsurableItem extends Transportable, Sellable { /** Returns insured Value in cents */ public int insuredValue(); } This interface mixes the methods of the Transportable interface with the methods of the Sellable interface, and adds an extra method, insuredValue Such an interface... could transport For such objects, we define the interface shown in Code Fragment 2. 10 Code Fragment 2. 8: Interface Sellable Code Fragment 2. 9 : Class Photograph implementing the Sellable interface 117 Code Fragment 2. 10: Interface Transportable We could then define the class BoxedItem, shown in Code Fragment 2. 11, for miscellaneous antiques that we can sell, pack, and ship Thus, the class BoxedItem implements . 2. 2.3 Using Inheritance in Java There are two primary ways of using inheritance of classes in Java, specialization and extension. Specialization In using specialization we are specializing. example, an instance of the Java class Integer stores, as an instance variable, an integer, and it provides several operations for accessing this data, including methods for converting it into other. method, herd, since Border Collies have a herding instinct that is not present in standard dogs. By adding the new method, we are extending the functionality of a standard dog. In Java, each class

Ngày đăng: 14/08/2014, 01:21

TỪ KHÓA LIÊN QUAN