Download Java Design Objects UML and Process 1st Edition Kirk Knoernschild ebook file with all chapters
Download Java Design Objects UML and Process 1st Edition Kirk Knoernschild ebook file with all chapters
https://ebookfinal.com/download/late-early-objects-big-java-5th-
edition-cay-horstmann/
https://ebookfinal.com/download/java-concepts-early-objects-7th-
edition-cay-s-horstmann/
https://ebookfinal.com/download/object-oriented-software-engineering-
using-uml-patterns-and-java-3rd-intern-edition-bruegge/
https://ebookfinal.com/download/systems-analysis-and-design-with-
uml-3rd-edition-alan-dennis/
Object Oriented Software Engineering Practical Software
Development using UML and Java Second Edition Timothy
Lethbridge
https://ebookfinal.com/download/object-oriented-software-engineering-
practical-software-development-using-uml-and-java-second-edition-
timothy-lethbridge/
https://ebookfinal.com/download/java-testing-design-and-automation-
frank-cohen/
https://ebookfinal.com/download/practical-object-oriented-design-with-
uml-2nd-edition-mark-priestley/
https://ebookfinal.com/download/java-design-patterns-1st-edition-mr-
devendra-singh/
https://ebookfinal.com/download/introduction-to-java-and-software-
design-1st-edition-nell-b-dale/
Java Design Objects UML and Process 1st Edition Kirk
Knoernschild Digital Instant Download
Author(s): Kirk Knoernschild
ISBN(s): 9780201750447, 0201750449
Edition: 1st
File Details: PDF, 2.52 MB
Year: 2001
Language: english
Java™ Design: Objects, UML, and Process
By Kirk Knoernschild
The first half of the book focuses on the software process and how UML,
Java technology, and object-oriented programming can be used effectively.
The advantages of each technology are enumerated, highlighting common
principles. Also included are in-depth discussions of design patterns, the
Unified Process, and Extreme Programming.
Armed with a fresh perception of current design tools, this book will give
you a deeper understanding of how to design cleaner Java applications
using UML. Learn how you can expand your developer's toolkit using
existing technologies in new ways--and create better software.
PREFACE
This book emphasizes the utilization of Java, the Unified Modeling Language (UML),
object-orientation, and software process as a cohesive whole. This book will help you
Intended Audience
This book discusses how the UML can be used during an implementation stage of the
software development lifecycle. With its emphasis on object orientation, problem
solving, and communication, this book will give developers a deeper understanding of
how to design cleaner Java applications. Much of the discussion is focused on
refactoring or cleaning up the design of existing code. Using these concepts,
developers can become more efficient in discovering more resilient solutions sooner.
Designers and architects can benefit by gaining a deeper understanding of how the
UML can be used to create a system of checks and balances when establishing
architectural restrictions and designing subsystems. These individuals will gain
insight into how our models serve as the mechanism to validate our systems'
architectures. The numerous principles and guidelines discussed also will help
contribute to more resilient systems, as well as serve as a measuring stick of our
existing object-oriented designs.
Project managers, IT managers, and project sponsors can benefit by obtaining a deeper
understanding of the importance of these key technologies. No longer will we view each of these
technologies as separate entities, but we'll see them as a set of complementary tools that can be
used together to contribute to a lower-risk development effort.
Feedback
I'm always interested in obtaining feedback from individuals reading this book. Feel
free to e-mail me the information you found most useful. But more importantly, I'm
interested in hearing how you feel this book could be improved. Such feedback can
ensure future readers obtain the knowledge needed to enhance their software
development efforts. I'll post additional information on this book at
www.kirkk.com/JOUP.html.
Acknowledgments
A very special thanks goes out to all of the thoughtful reviewers who contributed
significantly in helping to ensure the material in this book was both useful and
accurate. Most significantly, I would like to extend a personal thank you to Adam
Brace, John Brugge, Levi Cook, and David Williams. Their thoughtful reviews and
contributions played significant roles in my ability to complete this work.
In addition, I would like to thank Paul Becker, my editor. Without his constant
encouragement and patience, I no doubt would have been unable to complete this
work. Thank you to Debbie Lafferty, without whom I would not have been a part of
the Addison-Wesley family. I would like to thank Tyrrell Albaugh, my production
manager, for her careful guidance through the final editing stages of the manuscript.
And, of course, without the patience of Nancy Crumpton, some of my ill-formed
sentences and grammatical errors might not have been caught. Finally, I want to thank
the rest of the Addison-Wesley family, most of whom I did not have the honor of
meeting. They made significant contributions in making this book a reality.
Last, I thank those individuals, too many to name, who have contributed, no matter
how small, to my life. You know who you are!
Kirk Knoernschild
joup@kirkk.com
www.kirkk.com
INTRODUCTION
The convergence of a suite of technologies into a cohesive whole represents a
significant advantage over the same technologies standing independently. Java, object
orientation, the Unified Modeling Language (UML), and software process are
prominent technologies that contribute significantly to the success of software
development efforts. Yet used independently, their true power may not be realized.
Each of these four unique, yet complementary, technologies has a distinct form and
can be studied and applied independently. However, when used together as a set of
supporting technologies, we increase the likelihood of developing higher-quality,
on-time, and on-budget software that meets our business needs.
Our goal is to discuss the concepts that enable developers to use the UML, objects,
and software process to solve complex problems encountered during design and
implementation of enterprisewide Java development. We must ensure we realize the
maximum benefit of each of these powerful technologies by assembling and applying
the best practices as a cohesive whole. In addition, we must ignore, or at least use
more judiciously, those aspects that lack significant and immediate value.
Mechanisms that enable us to prove our systems are resilient, extensible, and
maintainable are sorely needed.
Taking this next step is not easy. It involves changes throughout the entire software
development lifecycle. By utilizing today's best-of-breed technologies, methodologies,
and principles, we can create the set of complementary tools to take our efforts to this
next level. Using these complementary tools creates a development effort with an
implicit system of checks and balances. These help ensure that our systems will, in
fact, be more flexible and resilient to change.
This book is not an in-depth study of all of the UML diagrams. We focus on those
diagrams that are used most often in the development lifecycle and those that
contribute most to application development. These diagrams often are incorporated
into a development environment that is adopting the UML for the first time.
This book doesn't present a formal software development process. Instead, we glean
best practices from a suite of proven software development processes. As such, while
our discussion constantly considers process, we're not interested in a particular
software development process, but instead those practices embodied within software
processes that focus on success.
It's recommended that the chapters in this book be read in order. The concepts in each
chapter build as the book progresses. If reading the chapters in order is not a viable
option for you, consider the following:
• For those most interested in Java and object orientation, Chapters 1 and 3 and
7 through 11 will be of most interest.
• Those readers wishing to explore software process and its relation to the UML
will find Chapters 4 through 6 most interesting.
• Those who desire to explore strictly the UML will find Chapters 2, 3, and 6
most applicable. What follows is a brief overview of the topics covered in
each chapter.
Chapter Synopsis
Chapter 1 introduces objects and the goal of object-oriented system design. Some of
the contents of this chapter may surprise you. We don't spend a lot of time introducing
the most fundamental concepts; instead, we discuss concepts such as polymorphism
and inheritance in terms of principles and patterns that serve as the context for much
of the discussion later in this book.
Chapter 2 provides a brief history of the UML, which we strongly feel is necessary in
order to fully understand it. We also introduce the primary goals of the UML and the
problems the UML helps to solve.
Chapter 3 introduces the Java programming language and its mappings to the UML.
We don't attempt to teach Java; instead, we focus on how various UML constructs can
be mapped to the Java programming language. We discuss modeling from a
conceptual level to form the basis for this discussion.
Chapter 4 discusses the important role that software process plays in the software
development effort. We discover the benefits associated with structuring our diagrams
in a way that enables us to more easily identify the problem we're trying to solve. We
introduce the best practices that any software development process should promote
and explain how the UML fits into these set of best practices.
Chapter 5 stands out in that we take a reprieve from our emphasis on the UML, Java,
and objects and discuss the many factors that contribute to the adoption of each of
these complementary technologies as a cohesive whole. This chapter examines many
of the considerations that should be taken into account for any team or organization
contemplating pragmatic integration of these newer technologies into their
development environment.
Chapter 6 begins our journey in the convergence of Java, the UML, objects, and
software process. In this chapter, we discuss some of the basic artifacts associated
with establishing our system's requirements. This chapter serves as the basis for later
discussions. While we don't elaborate in detail on the methods and practices used to
elicit and manage requirements, we do present a simple set of requirements and one
alternative to their formatting.
Chapter 7 works toward identifying the first set of analysis artifacts. By analyzing the
requirements presented in the previous chapter, we identify our initial analysis classes.
We categorize these as either boundary, entity, or control classes, which are used to
organize our abstractions according to their behavior. The result is a first attempt at
our system's design.
Chapter 8 emphasizes the dynamic aspects of our system. We introduce in more detail
the UML sequence diagram, and in addition to the syntactic elements on this diagram,
we also discuss many of the important decisions associated with allocating behavior
to our initial classes identified in Chapter 7.
Chapter 9 presents a discussion on the static aspects of our system. Based upon the
behaviors allocated to our objects, discussed in Chapter 8, and the object
collaborations, we're now better prepared to design our system's structure. The UML
class diagram is used throughout the majority of this chapter, and many important
design decisions are discussed. This chapter not only discusses the relations between
classes, but also presents package diagrams, which describe the relationships that
exist between the packages that compose our system.
Chapter 11 introduces subsystems and their unique nature. We also introduce the
important characteristics of a subsystem.
Appendix A presents the Rational Unified Process (RUP) and Extreme Programming
(XP). We discuss the similarities and differences between each of these popular
software development processes.
Appendix B discusses how the UML can be used with J2EE. In addition, this
appendix elaborates on how J2EE fits into the book's overall discussions.
Appendix C provides sample code for our first UML discussion found in Section 3.6
in Chapter 3.
When designing object-oriented systems, the challenges are numerous, and the
solutions are various. How do we identify an approach that will help ensure we are
creating an extensible, robust, and easily maintainable system? One way is by using
design patterns. Design patterns are proven design solutions that can be tailored to fit
the context of a particular design challenge. In essence, they are reusable design
templates. While the notion of patterns has hit mainstream development since the
seminal work published in 1995 by the Gang of Four [GOF95], the number of
patterns available has become almost unmanageable. So many patterns are available
today that attempting to find a pattern that can solve difficult design challenges
conceivably could take longer than discovering a new solution, which if designed
efficiently, is probably documented as a pattern somewhere anyway. When we can't
find a pattern that solves our challenges, we can take an approach during design that
will ensure we are solving our challenges correctly, given the absence of a readily
available pattern. Such approaches are based on some fundamental principles of
object orientation.
While these fundamental principles can provide helpful guidance when developing
object-oriented software, our understanding of object orientation must come first. It is
virtually impossible to apply a principle when we don't fully understand the value of
that principle. Therefore, we must understand not only the principles, but also the true
benefits of object orientation, as well as the goals that these benefits enable us to
effectively and gracefully achieve.
1.0 Principles, Patterns, and the OO Paradigm
By this time, we've all been saturated with the benefits of objects. Reuse is the Holy
Grail of object orientation. Unfortunately, a lot of the works discussing object
orientation exist at such a theoretical level that they can be difficult to interpret and
apply pragmatically, or these works exist at such a detailed level that it can be
difficult to derive a concise vision of the paradigm in its entirety. Understanding
concepts such as abstraction, inheritance, encapsulation, and polymorphism is
wonderful, but they are just concepts and don't provide much guidance in creating
more reusable and highly maintainable systems. In fact, our discussion in this book
assumes a basic understanding of these terms.
We can achieve reuse, create more flexible designs, and understand the
object-oriented paradigm more thoroughly by studying and applying patterns. But
even patterns don't serve as a guiding set of principles that are universally applicable,
and with the proliferation of patterns over the past couple of years, simply finding the
most appropriate pattern can be a daunting task. This begs some interesting questions.
What are the fundamental principles of the object-oriented paradigm? Is there a set of
guiding principles that we can consistently and faithfully apply to help us create more
robust systems? In fact there is, and we discuss the most useful principles in Section
1.1, later in this chapter.
Before we explore these principles, however, it's important to revisit the true benefit
of object orientation. We've been told that reuse is the nirvana of programming, and
object orientation provides it. The reason reuse has been so heavily touted is because
it impacts the bottom line. When we use easily pluggable objects, which are highly
reusable, we reduce the time required to develop applications. When we develop
faster, we develop more cheaply as well. Certainly, one of the benefits of object
orientation can be reuse; however, it may not be the most important benefit. In the
December 2000 issue of The Rational Edge, Walker Royce cited two interesting
statistics:
These statistics are astounding. The cost of maintaining a system is twice that of
developing it. This being the case, we need a paradigm that facilitates system
maintenance as much as, if not more than, reuse. Granted, effectively reusing objects
can help in reducing system maintenance, but it doesn't necessarily guarantee it. In
fact, consider the following:
Given a class R that is being reused by both classes A and B, if A requires new or
modified behaviors of R, it would make sense that any changes to R would be
reflected in B as well. While this is true, what happens if B does not desire this new
behavior? What if this new behavior defined for R actually broke B? In this case, we
have reuse, but we don't have a high degree of maintenance.
You might already be thinking of ways in which this scenario can be resolved. You
might be saying that you wouldn't have done it this way in the first place, and there
are certainly many ways to resolve the preceding problem. The granularity of the
method contributes greatly to the likelihood of its reusability. The fact remains that
each design is centered around flexibility, which brings us to Royce's second statistic
cited earlier. If we are spending roughly 15 percent of our time programming, what
are we spending the remaining 85 percent of our time doing? The answer is design, or
at least variations of what many of us associate with a traditional design phase in the
software development lifecycle. Of course, we also spend time managing
requirements, planning projects, and testing. Focusing strictly on the design activity,
had we designed the previously described example in a more effective manner, it is
likely that our maintenance cost would have been reduced. But it's this design aspect
that is so difficult. Therefore, following a set of guiding principles serves us well in
creating more flexible designs.
Java is one of the first languages to make explicit the difference between
interface and implementation inheritance. In Java, the extends keyword
exemplifies implementation inheritance (with a small amount of interface
inheritance through abstract methods), whereas the implements keyword
illustrates interface inheritance. Therefore, stating that Java doesn't support
multiple inheritance is not entirely true because Java does support multiple
inheritance of interfaces.
Ultimately, the design chosen for our software system will impact the maintainability
of our system. We call a design that impacts the maintainability of our system the
software's architecture, and designing a system with a resilient architecture is of
utmost importance. Because we know that requirements change, the resiliency of our
architecture will impact our system's survival. However, the ability of our system to
change, or grow to meet new requirements, and still survive are conflicting goals,
known as the architecture paradox [SUB99].
What Is Design?
We associate design with some activity or phase within a traditional software
development lifecycle. In this book, however, when we refer to design, we
refer to the set of best practices and principles of object orientation that are
continuously applied throughout all phases of the software development
lifecycle. We even imply that lifecycle phases such as requirements,
construction, and testing contain small slices of time where an emphasis is
placed upon the practices and principles of design.
Suppose we have a system that fulfills its full set of requirements. As the
requirements begin to change, the software begins to die, and its survival is
challenged. In order to restore its survivability, we need to change the software. With
each change, the software's architecture is compromised. As more changes are made,
the software becomes harder to maintain. Because changes become so difficult to
make, the costs associated with maintaining the system eventually reach a point where
future maintenance efforts cannot be justified or introduce new errors. At this point,
our system has lost its ability to grow, and it dies. Therefore, as depicted in Figure 1.1,
as changes increase, survivability decreases.
Figure 1.1. Architecture Paradox
This experience is a frustrating one, and it's common to blame others for these
changing requirements. However, businesses typically drive these changes, and we
shouldn't try to place the blame elsewhere. In fact, the problem is not with the
changing requirements. We already know from experience that requirements change.
A commonly quoted adage cites three certainties in life: death, taxes, and changing
requirements. The fact that requirements change and compromise our systems'
internal structures is not the fault of changing requirements, but the fault of our design.
Requirements always change, and it is our job to deal with it!
Today, the software development industry abounds with patterns, of which many
categories exist. Most of us have probably heard of patterns, and we will not devote
our discussion here to duplicating work that has already been successfully
documented. Instead, we provide an executive summary on patterns, including a few
examples later in this chapter (see Section 1.3).
Patterns come in many forms. Architectural patterns focus on driving the high-level
mechanisms that characterize an entire application. Analysis patterns help in solving
domain-dependent obstacles. Design patterns help us solve a broad range of technical
design challenges. We'll find that using patterns in conjunction with other patterns
typically contributes to the achievement of the most flexible, robust, and resilient
designs. Again, we'll see this firsthand as we progress throughout the book.
A design pattern systematically names, motivates, and explains a general design that
addresses a recurring design problem in object-oriented systems. It describes the
problem, the solution, when to apply the solution, and its consequences. It also gives
implementation hints and examples. The solution is a general arrangement of objects
and classes that solve the problem. The solution is customized and implemented to
solve the problem in a particular context. [GOF95]
Examining this definition further illustrates the potential of patterns. All patterns have
a name, which enables a developer to easily refer to, and communicate with, other
developers the intent of a particular pattern. Patterns help solve design challenges that
continually surface. Each situation, however, is invariably different in some regards.
A well-documented pattern describes the consequences of using it, as well as
providing hints and examples of how to effectively utilize it. Consequently, when we
use a pattern, it is unlikely that we'll implement it in the exact same manner each time.
Patterns can be thought of as algorithms for design. Certain algorithms have slight
differences based on the implementation language, just as patterns vary based on the
context in which they're applied. Most developers who have written sorting routines
can understand the basic algorithm associated with the term bubble sort. Similarly,
those familiar with patterns understand the structure and intent of a Strategy pattern.
This naming convention is a benefit of using patterns because they enable us to
communicate complex designs more effectively. Many more benefits are associated
with the use of patterns, such as taking advantage of proven designs, creating more
consistent designs, and providing a more concrete place to start when designing.
Patterns typically are discovered by some of the most talented object- oriented
developers in the world. These patterns usually go through an intensive review and
editing cycle, and thus they are proven design solutions. The review and editing cycle
enables less-experienced developers to gain insights that will make their own designs
as flexible as those of an experienced developer. In fact, the review and editing cycle
may be the single greatest benefit associated with using patterns, because they are
essentially the collective work of the most experienced designers in the
object-oriented community.
Although the value of patterns is real, realizing this value also implies knowing which
pattern is appropriate to use in a specific context, and how it can be applied. Because
of the proliferation of patterns, it can be difficult to efficiently find a pattern that best
fits a need. Principles, in comparison to patterns, exist at a higher level. The majority
of patterns adhere to an underlying set of principles. In this regard, we can think of
patterns as being instances of our principles. Principles are at the heart of
object-oriented design. The more patterns we understand, the more design alternatives
we can consider when architecting our systems. It's highly unlikely, however, that
we'll ever completely understand, or even have knowledge of, all of the patterns that
have been documented. By adhering to a more fundamental set of principles, it's
likely that we'll encounter patterns that are new to us 梡 atterns that may have been
documented but that we aren't aware of. Or we may even discover new patterns. The
point is that while patterns provide a proven starting point when designing, principles
lie at the heart of what we need to accomplish when designing resilient, robust, and
maintainable systems. Understanding these principles not only enhances our
understanding of the object-oriented paradigm, but also helps us understand more
about patterns, when to apply them, and the foundation upon which patterns are built.
When applying these principles to Java, they can be broken into two categories. The
first category focuses on relationships that exist between classes. These principles
typically form the foundation of many design patterns. The second category of
principles focuses on relationships between packages. These principles form the
foundation of many architectural patterns. Keep in mind that at this point, we are
primarily concerned with understanding the core concepts present within these
principles. Application of these principles typically is dependent on a set of guiding
heuristics, which we will continually elaborate on, and refine, as we progress
throughout the book.
The Open Closed Principle (OCP) is undoubtedly the most important of all the class
category principles. In fact, each of the remaining class principles are derived from
OCP. It originated from the work of Bertrand Meyer, who is recognized as an
authority on the object-oriented paradigm [OOSC97]. OCP states that we should be
able to add new features to our system without having to modify our set of preexisting
classes. As stated previously, one of the benefits of the object-oriented paradigm is to
enable us to add new data structures to our system without having to modify the
existing system's code base.
Let's look at an example to see how this can be done. Consider a financial institution
where we have to accommodate different types of accounts to which individuals can
make deposits. Figure 1.2 shows a class diagram with accompanying descriptions of
some of the elements and how we might structure a portion of our system. We discuss
in detail the elements that make up various diagrams and the Unified Modeling
Language (UML) in general in Chapter 3. For the purposes of our discussion in this
chapter, we focus on how the OCP can be used to extend the system.
Figure 1.2. Open Closed Principle (OCP)
Our Account class has a relationship to our AccountType abstract class. In other
words, our Account class is coupled at the abstract level to the AccountType
inheritance hierarchy. Because both our Savings and Checking classes inherit
from the AccountType class, we know that through dynamic binding, we can
substitute instances of either of these classes wherever the AccountType class is
referenced. Thus, Savings and Checking can be freely substituted for
AccountType within the Account class. This is the intent of an abstract class and
enables us to effectively adhere to OCP by creating a contract between the Account
class and the AccountType descendents. Because our Account isn't directly
coupled to either of the concrete Savings or Checking classes, we can extend the
AccountType class, creating a new class such as MoneyMarket, without having
to modify our Account class. We have achieved OCP and now can extend our
system without modify its existing code base.
Therefore, one of the tenets of OCP is to reduce the coupling between classes to the
abstract level. Instead of creating relationships between two concrete classes, we
create relationships between a concrete class and an abstract class, or in Java, between
a concrete class and an interface. When we create an extension of our base class,
assuming we adhere to the public methods and their respective signatures defined on
the abstract class, we essentially have achieved OCP. Let's take a look at a simplified
version of the Java code for Figure 1.2, focusing on how we achieve OCP, instead of
on the actual method implementations.
This is the abstract AccountType class that serves as the contract between our
Account class and AccountType descendents. The deposit method is the
contract.
We mentioned in our previous discussion that OCP is the most important of the class
category principles. We can think of the Liskov Substitution Principle (LSP) as an
extension to OCP. In order to take advantage of LSP, we must adhere to OCP because
violations of LSP also are violations of OCP, but not vice versa. LSP is the work of
Barbara Liskov and is derived from Bertrand Meyer's Design by Contract.[1] In its
simplest form, LSP is difficult to differentiate from OCP, but a subtle difference does
exist. OCP is centered around abstract coupling. LSP, while also heavily dependent
on abstract coupling, is in addition heavily dependent on preconditions and
postconditions, which is LSP's relation to Design by Contract, where the concept of
preconditions and postconditions was formalized.
[1]
A concept that Bertrand Meyer built into the Eiffel programming language and discusses in Object-Oriented Software Construction.
See [OOSC97].
This method declaration isn't allowed because the Exception class thrown in this
method is the ancestor of the InvalidAmountException thrown previously.
Again, we can't throw an exception in a method on a subclass that exists at a higher
level of abstraction than the exception thrown by the base class method we are
overriding. On the other hand, reversing these two method signatures would have
been perfectly acceptable to the Java compiler. We can throw an exception in an
overridden subclass method that is at a lower level of abstraction than the exception
thrown in the ancestor. While this does not correspond directly to the concept of
preconditions and postconditions, it does capture the essence. Therefore, we can state
that any precondition stipulated by a subclass method can't be stronger than the base
class method. Also, any postcondition stipulated by a subclass method can't be weaker
than the base class method.
To adhere to LSP in Java, we must make sure that developers define preconditions
and postconditions for each of the methods on an abstract class. When defining our
subclasses, we must adhere to these preconditions and postconditions. If we do not
define preconditions and postconditions for our methods, it becomes virtually
impossible to find violations of LSP. Suffice it to say, in the majority of cases, OCP
will be our guiding principle.
At this point, there exists a striking similarity between DIP and OCP. In fact, these
two principles are closely related. Fundamentally, DIP tells us how we can adhere to
OCP. Or, stated differently, if OCP is the desired end, DIP is the means through
which we achieve that end. While this statement may seem obvious, we commonly
violate DIP in a certain situation and don't even realize it.
The first way to resolve this impasse is to dynamically load the object using the
Class class and its newInstance method. However, this solution can be
problematic and somewhat inflexible. Because DIP doesn't enable us to refer to the
concrete class explicitly, we must use a String representation of the concrete class.
For instance, consider the following:
Class c = Class.forName("SomeDescendent");
SomeAncestor sa = (SomeAncestor) c.newInstance();
Abstract Coupling
Abstract coupling is the notion that a class is not coupled to another concrete
class or class that can be instantiated. Instead, the class is coupled to other
base, or abstract, classes. In Java, this abstract class can be either a class with
the abstract modifier or a Java interface data type. Regardless, this concept
actually is the means through which LSP achieves its flexibility, the
mechanism required for DIP, and the heart of OCP.
Another approach to resolving the object creation challenge is to use an object factory.
Here, we create a separate class whose only responsibility is to create instances. This
way, our original class, where the instance previously would have been created, stays
clear of any references to concrete classes, which have been removed and placed in
this factory. The only references contained within this class are to abstract, or base,
classes. The factory does, however, reference the concrete classes, which is, in fact, a
blatant violation of DIP. However, it's an isolated and carefully thought through
violation and is therefore acceptable.
Keep in mind that we may not always need to use an object factory. Along with the
flexibility of a factory comes the complexity of a more dynamic collaboration of
objects. Concrete references aren't always a bad thing. If the class to which we are
referring is a stable class, not likely to undergo many changes, using a factory adds
unwarranted complexity to our system. If a factory is deemed necessary, the design of
the factory itself should be given careful consideration. This factory pattern has many
design variants, some of which are discussed later in this book (see Chapter 9).
Put simply, any interface we define should be highly cohesive. In Java, we know that
an interface is a reference data type that can have method declarations, but no
implementation. In essence, an interface is an abstract class with all abstract methods.
As we define our interfaces, it becomes important that we clearly understand the role
the interface plays within the context of our application. In fact, interfaces provide
flexibility: They allow objects to assume the data type of the interface. Consequently,
an interface is simply a role that an object plays at some point throughout its lifetime.
It follows, rather logically, that when defining the operation on an interface, we
should do so in a manner that doesn't accommodate multiple roles. Therefore, an
interface should be responsible for allowing an object to assume a single role,
assuming the class of which that object is an instance implements that interface.
At first glance, the design depicted in Figure 1.3 seemed plausible. After further
investigation, however, questions were raised as to the cohesion of the
RowSetManager interface. What if classes implementing this interface were
read-only and didn't need insert and update functionality? Also, what if the data client
weren't interested in retrieving the data, but only in iterating its already retrieved
internal data set? Exploring these questions a bit further, and carefully considering the
Interface Segregation Principle (ISP), we found that it was meaningful to have a data
structure that wasn't even dependent on a retrieve action at all. For instance, we may
wish to use a data set that was cached in memory and wasn't dependent on an
underlying physical data source. This led us to the design in Figure 1.4.
Figure 1.4. Compliance to Interface Segregation Principle (ISP)
The Composite Reuse Principle (CRP) prevents us from making one of the most
catastrophic mistakes that contribute to the demise of an object-oriented system: using
inheritance as the primary reuse mechanism. The first reference to this principle was
in [GOF95]. For example, let's turn back to a section of our diagram in Figure 1.2. In
Figure 1.5, we see the AccountType hierarchy with a few additional attributes and
methods. In this example, we have added a method to the ancestor AccountType
class that calculates the interest for each of our accounts. This approach seems to be a
good one because both our Savings and MoneyMarket classes are
interest-bearing accounts. Our Checking class is representative of an account that
isn't interest bearing. Regardless, we justify this by convincing ourselves that it's
better to define some default behavior on an ancestor and override it on descendents
instead of duplicating the behavior across descendents. We know that we can simply
define a null operation on our Checking class that doesn't actually calculate interest,
and our problem is solved. While we do want to reuse our code, and we can prevent
the Checking class from calculating interest, our implementation contains a tragic
flaw. First, let's discuss the flaw and when it will surface. Then we'll discuss why this
problem has occurred.
The third solution is the one we should have used up front. Because we realized that
the calculation of interest wasn't common to all classes, we shouldn't have defined any
default behavior in our ancestor class. Doing so in any situation inevitably results in
the previously described outcome. Let's now resolve this problem using CRP.
In Figure 1.6, we see a depiction of our class structure utilizing CRP. In this example,
we have no default behavior defined for calculateInterest in our
AccountType hierarchy. Instead, in our calculateInterest methods on both
our MoneyMarket and Savings classes, we defer the calculation of interest to a
class that implements the InterestCalculator interface. When we add our
Stock class, we now simply choose the Inter estCalculator that is
applicable for this new class or define a new one if it's needed. If any of our other
classes need to redefine their algorithms, we can do so because we are abstractly
coupled to our interface and can substitute any of the classes that implement the
interface anywhere the interface is referenced. Therefore, this solution is ultimately
flexible in how it enables us to calculate interest. This is an example of CRP. Each of
our MoneyMarket and Savings classes are composed of our
InterestCalculator, which is the composite. Because we are abstractly
coupled, we easily see we can receive polymorphic behavior. Hence, we have used
polymorphic composition instead of inheritance to achieve reuse.
Figure 1.6. Compliance to Composite Reuse Principle (CRP)
At this point, you might say, however, that we still have to duplicate some code across
the Stock and MoneyMarket classes. While this is true, the solution still solves
our initial problem, which is how to easily accommodate new interest calculation
algorithms. Yet an even more flexible solution is available, and one that will enable us
to be even more dynamic in how we configure our objects with an instance of
InterestCalculator.
So how did we fall into the original trap depicted in Figure 1.5? The problem lies
within the inheritance relationship. Inheritance can be thought of as a generalization
over a specialization relationship梩hat is, a class higher in the inheritance hierarchy is
a more general version of those inherited from it. In other words, any ancestor class is
a partial descriptor that should define some default characteristics that are applicable
to any class inherited from it. Violating this convention almost always results in the
situation described previously. In fact, any time we have to override default behavior
defined in an ancestor class, we are saying that the ancestor class is not a more
general version of all of its descendents but actually contains descriptor characteristics
that make it too specialized to serve as the ancestor of the class in question. Therefore,
if we choose to define default behavior on an ancestor, it should be general enough to
apply to all of its descendents.
The obvious disadvantage associated with PLK is that we must create many methods
that only forward method calls to the containing classes internal components. This can
contribute to a large and cumbersome public interface. An alternative to PLK, or a
variation on its implementation, is to obtain a reference to an object via a method call,
with the restriction that any time this is done, the type of the reference obtained is
always an interface data type. This is more flexible because we aren't binding
ourselves directly to the concrete implementation of a complex object, but instead are
dependent only on the abstractions of which the complex object is composed. In fact,
this is how many classes in Java typically resolve this situation.
It isn't uncommon to find that many developers haven't realized that relationships do
exist among the packages within a Java application. The dependencies between
packages often go unnoticed. Logically, however, if a class contains relationships to
other classes, then packages containing those classes also must contain relationships
to other packages. These package relationships can tell us a great deal about the
resiliency of our system, and the principles discussed in Sections 1.2.2 through 1.2.7
enable us to more objectively measure the robustness of our package relationships.
First, let's examine what is meant by a package dependency. In Figure 1.8, we see a
class diagram depicting two packages, A and B. Within each of these packages exist
two classes. Class Client exists in package A and class Service in B. Simply
stated, if class Client references in any way class Service, then it must hold true
that Client has a structural relationship to class Service, which implies that any
changes to the Service class may impact Client. Figure 1.8 illustrates how this
relationship exists between packages, classes, and source code.
Figure 1.8. Package and Corresponding Class Relationships
A Subtle Relation
If class Client has a relation to class Service, then it's obvious that the
packages containing these two classes also have a relationship, formally
known as a package dependency. It's not so obvious that these class and
package relationships can be considered two separate views of our system.
One is a higher-level package view, the other a lower-level class view. In
addition, these two views serve to validate each other. You'll find
information on this subject in Chapter 10.
Let's examine this relationship from a different viewpoint. If the contents of package
A are dependent on the contents of package B, then A has a dependency on B; and if
the contents of B change, this impact may be noticeable in A. Therefore, the
relationships between packages become more apparent, and we can conclude the
following:
If changing the contents of a package P1 may impact the contents of another package
P2, we can say that P1 has a package dependency on P2.
Packages may contain not only classes, however, but also packages. In Java,
importing the contents of a package implies we have access only to the classes within
that package and don't have access to classes in any nested packages. The Unified
Modeling Language (UML), however, doesn't take any formal position on nested
packages. The question of how to deal with nested packages is left to the development
team. We use the terms opaque and transparent to define the two options. Opaque
visibility implies that a dependency on a package with nested packages doesn't imply
access to these nested packages. Transparent visibility, on the other hand, does carry
with it implicit dependencies.
Because the UML takes no formal position, development teams must define how they
wish to deal with package dependencies. Several options are available. First, teams
may take their own position and state that all package dependencies are either opaque
or transparent. Any variation from this norm must be modeled explicitly. In situations
such as these, we recommend selecting opaque. Adopting transparent visibility
doesn't enable us to restrict access to nested packages. On the other hand, if opaque is
adopted as a standard, we can always explicitly model relations to nested packages on
separate diagrams. For purposes of discussion throughout this book, we assume all
package dependency relationships are opaque.
Whenever a client class wishes to use the services of another class, we must reference
the class offering the desired services. This should be apparent from our previous
discussions and is the basis upon which package relationships exist. If the class
offering the service is in the same package as the client, we can reference that class
using the simple name. If, however, the service class is in a different package, then
any references to that class must be done using the class' fully qualified name, which
includes the name of the package.
We also know that any Java class may reside in only a single package. Therefore, if a
client wishes to utilize the services of a class, not only must we reference the class,
but we must also explicitly make reference to the containing package. Failure to do so
results in compile-time errors. Therefore, to deploy any class, we must be sure the
containing package is deployed. Because the package is deployed, we can utilize the
services offered by any public class within the package. Therefore, while we may
presently need the services of only a single class in the containing package, the
services of all classes are available to us. Consequently, our unit of release is our unit
of reuse, resulting in the Release Reuse Equivalency Principle (REP). This leads us to
the basis for this principle, and it should now be apparent that the packages into which
classes are placed have a tremendous impact on reuse. Careful consideration must be
given to the allocation of classes to packages.
The basis for the Common Closure Principle (CCP) is rather simple. Adhering to
fundamental programming best practices should take place throughout the entire
system. Functional cohesion emphasizes well-written methods that are more easily
maintained. Class cohesion stresses the importance of creating classes that are
functionally sound and don't cross responsibility boundaries. And package cohesion
focuses on the classes within each package, emphasizing the overall services offered
by entire packages.
During development, when a change to one class may dictate changes to another class,
it's preferred that these two classes be placed in the same package. Conceptually, CCP
may be easy to understand; however, applying it can be difficult because the only way
that we can group classes together in this manner is when we can predictably
determine the changes that might occur and the effect that those changes might have
on any dependent classes. Predictions often are incorrect or aren't ever realized.
Regardless, placement of classes into respective packages should be a conscious
decision that is driven not only by the relationships between classes, but also by the
cohesive nature of a set of classes working together.
If we need the services offered by a class, we must import the package containing the
necessary classes. As we stated previously in our discussion of REP (see Section
1.2.2), when we import a package, we also may utilize the services offered by any
public class within the package. In addition, changing the behavior of any class within
the service package has the potential to break the client. Even if the client doesn't
directly reference the modified class in the service package, other classes in the
service package being used by clients may reference the modified class. This creates
indirect dependencies between the client and the modified class that can be the cause
of mysterious behavior. In fact, we can state the following:
If a class is dependent on another class in a different package, then it is, in fact,
dependent on all classes in that package, albeit indirectly.
This principle has a negative connotation. It doesn't hold true that classes that are
reused together should reside together, depending on CCP. Even though classes may
always be reused together, they may not always change together. In striving to adhere
to CCP, separating a set of classes based on their likelihood to change together should
be given careful consideration. Of course, this impacts REP because now multiple
packages must be deployed to use this functionality. Experience tells us that adhering
to one of these principles may impact the ability to adhere to another. Whereas REP
and Common Reuse Principle (CReP) emphasize reuse, CCP emphasizes
maintenance.
package A; package B;
import B.*; import A.*;
public class SomeAClass { public class SomeBClass {
private ClassInB b; private ClassInA a;
} }
Figure 1.9. Violation of Acyclic Dependencies Principles (ADP)
The problem with this code is that, because the classes in these packages are coupled,
the two packages become tightly coupled, which has a tremendous impact on REP. If
some class C in a different package, call it X, uses SomeBClass in package B, it
definitely implies that when package B is deployed, package A also must be deployed
because SomeBClass is coupled to SomeAClass in package A. Neglecting to
deploy package A with B results in runtime errors. In fact, were an application to have
cyclic dependencies among the packages that compose it, REP would be so negatively
impacted that all of these classes may as well exist in the same package. Obviously,
we wouldn't desire this degree of coupling because CCP also would be severely
compromised. Regardless, when develop ing Java applications, we should rarely find
ourselves in a situation where we have violated the Acyclic Dependencies Principle
(ADP). The consequences of doing so are dire, and we should avoid it at all costs.
If we do identify cyclic dependencies, the easiest way to resolve them is to factor out
the classes that cause the dependency structure. This is exactly what we have done in
Figure 1.10. Factoring out the classes that caused the cyclic dependencies has a
positive impact on reuse. Now, should we decide to reuse package B in our previous
example, we still need to deploy package A`, but we don't need to deploy package A.
The impact of this situation is not fully realized until we take into consideration more
subtle cycle dependencies, such as the indirect cyclic dependency illustrated at the
right in Figure 1.9, and its subsequent resolution in the diagram at right in Figure
1.10.
Figure 1.10. Acyclic Dependencies Principles (ADP) Compliance
At first glance, the Stable Dependencies Principle (SDP) seems to be stating the
obvious. However, exploring more deeply, we find the SDP contains an interesting
underlying message. In the context of software development, stability often is used to
describe a system that is robust, bug free, and rich in structure. In a more general
sense, stability implies that an item is fixed, permanent, and unvarying. Attempting to
change an item that is stable is more difficult than inflicting change on an item in a
less stable state. Applying this richer meaning of stability to software implies that
stable software is difficult to change. Before we revolt, however, let's point out that
simply because software is stable doesn't mean that it's riddled with bugs. Stable
software can certainly be robust, bug free, and rich in structure. Subsequently, the
stability of our software system isn't necessarily related to its quality. Less stable
software can be of high quality, yet it also can easily experience change. Stability is a
characteristic indicating the ease with which a system can undergo change, and with
Java, we are most concerned with the resiliency of our packages.
At this point, it's useful to ask what makes a package difficult to change. Aside from
poorly written code, the degree of coupling to other packages has a dramatic impact
on the ease of change. Those packages with many incoming dependencies have many
other components in our application dependent on them. These more stable packages
are difficult to change because of the far-reaching consequences the change may have
throughout all other dependent packages. On the other hand, packages with few
incoming dependencies are easier to change. Those packages with few incoming
dependencies most likely will have more outgoing dependencies. A package with no
incoming or outgoing dependencies is useless and isn't part of an application because
it has no relationships. Therefore, packages with fewer incoming, and more outgoing
dependencies, are less stable. Referring again to Figure 1.10, we can say that package
A` is a more stable package, whereas package A is a less stable package, taking into
consideration only the ease with which either of these packages can undergo change.
In previous sections, we've discussed that our software should be resilient and easily
maintainable. Because of this, our assumptions lead us to believe that all software
should be less stable, but this belief isn't always correct. Stability doesn't provide any
implication as to the frequency with which the contents of a package change. Those
packages having a tendency to change more often should be the packages that are less
stable in nature. On the other hand, packages unlikely to experience change may be
more stable, and it's in this direction that we should find the dependency relations
flowing. Combining the concepts of stability, frequency of change, and dependency
management, we're able to conclude the following:
It should now be obvious that we naturally depend in the direction of stability because
the direction of our dependency makes the packages more or less stable. Any
dependency introduced, however, should be a conscious decision, and one that we
know may have a dramatic impact on the stability of our application. Ideally,
dependencies should be introduced only to packages that are more stable. The
conscious nature of this decision is captured by our next principle, which describes
the technique we employ to create more stable or less stable packages.
Up to this point, we've been carefully referring to the stability of packages as either
more stable or less stable. Packages are typically not characterized as stable or
unstable. Instead, stability is a metric that can be measured and is a numerical value
between 0 and 1. The stability of a package can be measured using some fairly
straightforward calculations. Consider the following formula:
Assuming we do wish to depend in the direction of stability, we're left with no choice
but to structure packages so that the less stable packages exist atop a package
hierarchy, and more stable packages exist at the bottom of our package hierarchy. The
diagram in Figure 1.10 is indicative of this relationship. At this point, it's extremely
important that the packages that are lower in our package hierarchy must be the most
resilient packages in our system, because of the far-reaching consequences of
changing them.
As we've discussed, one of the greatest benefits of object orientation is the ability to
easily maintain our systems. The high degree of resiliency and maintainability is
achieved through abstract coupling. By coupling concrete classes to abstract classes,
we can extend these abstract classes and provide new system functions without having
to modify existing system structure. Consequently, the means through which we can
depend in the direction of stability, and help ensure that these more depended-upon
packages exhibit a higher degree of stability, is to place abstract classes, or interfaces,
in the more stable packages. We can now state the following:
A simple metric can help determine the degree of abstractness associated with a
package. Consider the following formula:
It is ideal if the abstractness of a package is either 1 or 0 and as far away from 0.5 as
possible. A value of 0.5 implies that a package contains both abstract and concrete
classes and, therefore, is neither stable nor instable. A goal of all packages should be a
high degree or low degree of abstractness, depending heavily upon its role within the
application.
It now should be apparent that any packages containing all abstract classes with no
incoming dependencies are utterly useless. On the other hand, packages containing all
concrete classes with many incoming dependencies are extremely difficult to maintain.
Therefore, in terms of SDP and the Stable Abstractions Principle (SAP), we can only
conclude that as abstractness (A) increases, instability (I) decreases.
1.3 Patterns
Any discussion of patterns could easily fill multiple texts. This section doesn't even
attempt to define a fraction of the patterns that can be useful during development.
Instead, we emphasize the common components of a pattern, as well as introduce a
few common patterns that have multiple uses. As the discussion continues throughout
this book, additional patterns are introduced as the need warrants. The discussion in
this section serves two purposes. First, we describe the intent of the patterns, a few
problems that they might help resolve, and some consequences of using the pattern.
This discussion should help in understanding how patterns can be used and the
context in which they might be useful. Second, and most important for this discussion,
we explore the consistent nature with which the principles previously discussed
resurface within these patterns. This topic is important because, as mentioned
previously, patterns may not always be available for the specific problem domain or,
if available, may possibly be unknown. In these situations, a reliance upon some other
fundamental principles is important.
1.3.1 Strategy
Undoubtedly, the Strategy pattern is one of the simplest patterns and is a direct
implementation of OCP. The purpose of a Strategy, as stated in [GOF95], is to
Define a family of algorithms, encapsulate each one, and make them interchangeable.
Also note that when using a Strategy, we have to determine when and where to use it.
Obviously, numerous places could take advantage of a Strategy. The trick is to keep
OCP in mind. Does the system need to have this flexibility at this point? The intent is
not to use Strategy, or any other pattern for that matter, anywhere that it could be used,
but to use the appropriate pattern in the appropriate context. Should the context call
for this degree of flexibility, a Strategy should be considered. Were we not familiar
with the Strategy pattern, nor any other pattern that accommodated the need, reliance
upon the fundamental principles would have yielded similar results. In fact, our
discussion in Section 1.1.5 resulted in the derivation of the Strategy pattern, prior to
ever having heard of Strategy.
1.3.2 Visitor
The Visitor pattern is not widely used, yet it serves as an excellent example
illustrating the true power of the object-oriented paradigm. The discussion up to this
point has focused on the fact that the object-oriented paradigm allows a system to be
easily extended with new classes, without having to modify the existing system. The
structured paradigm didn't accommodate this need in the same flexible manner. In fact,
it already has been clearly illustrated in Figure 1.7 that a system can be extended
Exploring the Variety of Random
Documents with Different Content
Telemachus (te lem a kus). Northern equivalent, 281.
Teuton (tū´ton). Ostara, a goddess, 58.
Teutonic Gods, 209, 211.
Thanatos (than´a-tos). Same as Hel, 288.
Theseus (thē´sūs). Northern equivalent, 291, 292.
Thetis (thē´tis). Northern equivalent for, 286.
Thialfi (te-älf´e). Servant of, 69, 70, 72, 80;
duel of, 74, 75;
Egil’s son, 174.
Thiassi (te-äs´se). Loki’s adventure with, 101;
Idun kidnapped, 102, 103, 104, 107–109, 199, 283;
Loki pursued by, 104, 108;
Gerda, relative of, 114;
the eyes of, 283, 284.
Thing (ting). Northern popular assembly, 30, 128, 129.
Thok (tok). Loki as, 194, 196, 204;
comparison, 289.
Thor (thôr or tôr). Never crosses Bifröst, 21;
Jörd, mother of, 43;
toast to, 46;
god of thunder, 61–83;
infancy of, 61;
anger of, 61, 65;
description of, 62;
hat of, 64;
Alvis petrified by, 65;
Miölnir given to, 68;
drinking wager of, 72;
duel with Hrungnir, 74;
adventure with Geirrod, 80;
temples and statues of, 82;
Tyr like, 84;
giants hated by, 113, 211;
Yule sacred to, 118;
Brisinga-men worn by, 127;
Uller, stepson of, 131;
Grid’s gauntlet helps, 148;
kettle secured by, 174;
goes fishing, 175, 176, 177;
consecrates Balder’s pyre, 191;
visits Utgard-loki, 198;
slays architect, 204;
threatens Loki, 206;
slays Midgard snake, 269;
sons of, 271;
Greek equivalents, 281, 282, 290.
Thora (tō´rȧ). Wife of Elf, daughter of Hakon, 256.
Thorburn. Origin of name, 81.
Thorn of Sleep. Brunhild stung by, 248.
Thorwaldsen (tôr´wald-sn). Origin of name, 81.
Thrall. Birth of, 141, 142.
Thridi (trē´dē). One of the trilogy, 44.
Throndhjeim (trōnd´yem). Temple of Frey at, 118.
Thrud (tro͞ od). Thor’s daughter, 64, 65.
Thrudgelmir (tro͞ od-gel´mir). Birth of giant, 12.
Thrud-heim (tro͞ od´hīm). Thor’s realm, 61.
Thrung (tro͞ ong). Freya, 125.
Thrym (trim). Thor visits, 77, 78, 281, 282;
Freya refuses, 129;
son of Kari, 212.
Thrym-heim (trim´hīm). Home of Thiassi, 102;
Loki visits, 103, 104.
Thunderer. Same as Odin, 277.
Thunderhill. Named after Thor, 81.
Thuringia (thū-rin´ji-ȧ). Hörselberg in, 56;
giants in, 215.
Thursday. Sacred to Thor, 82, 282.
Thurses (to͞ ors´ez). Giants called, 210.
Thvera (tvā´rȧ). Temple of Frey at, 118.
T (t ē´ti) B ld h F i i b d 92
Thviti (tvē´ti). Bowlder where Fenris is bound, 92.
Thyr (tir or tēr). Wife of Thrall, 141.
Titania. Queen of fairies, 223.
Titans. Northern equivalents for, 275, 283, 290.
Tityus (tit´i-us). Northern equivalent, 289.
Tiu (tū). Same as Tyr, 84, 282.
Toasts. To Odin, 45;
to Frigga, 46;
to Bragi, 99;
to Niörd and Frey, 111;
to Freya, 130.
Torge (tôr´ge). Story of giant, 213.
Torghatten (torg-hat´ten). Mountain, 213.
Tree Maidens. Elves same as, 223.
Trent. Superstition along the, 173.
Trolls. Dwarfs known as, 18, 213, 217, 220, 291.
Troy. Northern equivalent for siege of, 280.
Tübingen (tē´bing-en). Worship of Tyr in, 92.
Tuesday. Tyr’s day, 84.
Twelfth-night. Wild Hunt at, 31;
festival, 59.
Twilight of the Gods, 263, 273.
Tyr (tēr). Son of Frigga, 43;
god of war, 84–92;
one arm, 88, 267;
feeds Fenris, 89;
like Frey, 112;
like Irmin, 144;
chains Fenris, 166;
accompanies Thor, 174–177;
fights Garm, 268;
death of, 269.
Tyrfing (tēr´fing). Magic sword, 219.
Tyrol (tĭr´ul). Story of flax in, 54.
T ’ H A it ll d 92
Tyr’s Helm. Aconite called, 92.
1.D. The copyright laws of the place where you are located also
govern what you can do with this work. Copyright laws in most
countries are in a constant state of change. If you are outside
the United States, check the laws of your country in addition to
the terms of this agreement before downloading, copying,
displaying, performing, distributing or creating derivative works
based on this work or any other Project Gutenberg™ work. The
Foundation makes no representations concerning the copyright
status of any work in any country other than the United States.
1.E.6. You may convert to and distribute this work in any binary,
compressed, marked up, nonproprietary or proprietary form,
including any word processing or hypertext form. However, if
you provide access to or distribute copies of a Project
Gutenberg™ work in a format other than “Plain Vanilla ASCII” or
other format used in the official version posted on the official
Project Gutenberg™ website (www.gutenberg.org), you must, at
no additional cost, fee or expense to the user, provide a copy, a
means of exporting a copy, or a means of obtaining a copy upon
request, of the work in its original “Plain Vanilla ASCII” or other
form. Any alternate format must include the full Project
Gutenberg™ License as specified in paragraph 1.E.1.
• You pay a royalty fee of 20% of the gross profits you derive from
the use of Project Gutenberg™ works calculated using the
method you already use to calculate your applicable taxes. The
fee is owed to the owner of the Project Gutenberg™ trademark,
but he has agreed to donate royalties under this paragraph to
the Project Gutenberg Literary Archive Foundation. Royalty
payments must be paid within 60 days following each date on
which you prepare (or are legally required to prepare) your
periodic tax returns. Royalty payments should be clearly marked
as such and sent to the Project Gutenberg Literary Archive
Foundation at the address specified in Section 4, “Information
about donations to the Project Gutenberg Literary Archive
Foundation.”
• You comply with all other terms of this agreement for free
distribution of Project Gutenberg™ works.
1.F.
Our website is not just a platform for buying books, but a bridge
connecting readers to the timeless values of culture and wisdom. With
an elegant, user-friendly interface and an intelligent search system,
we are committed to providing a quick and convenient shopping
experience. Additionally, our special promotions and home delivery
services ensure that you save time and fully enjoy the joy of reading.
ebookfinal.com