📄 tij0177.html
字号:
<html><body>
<table width="100%"><tr>
<td>
<a href="http://www.bruceeckel.com/javabook.html">Bruce Eckel's Thinking in Java</a>
</td>
<td align="right">
<a href="tij_c.html">Contents</a> | <a href="tij0176.html">Prev</a> | <a href="tij0178.html">Next</a>
</td>
</tr></table>
<hr>
<H2 ALIGN=LEFT>
Improving
the design
</H2>
<DIV ALIGN=LEFT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">The
solutions in
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><I>Design
Patterns
</I></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
are organized around the question “What will change as this program
evolves?” This is usually the most important question that you can ask
about any design. If you can build your system around the answer, the results
will be two-pronged: not only will your system allow easy (and inexpensive)
maintenance, but you might also produce components that are reusable, so that
other systems can be built more cheaply. This is the promise of object-oriented
programming, but it doesn’t happen automatically; it requires thought and
insight on your part. In this section we’ll see how this process can
happen during the refinement of a system.
</FONT><P></DIV><DIV ALIGN=LEFT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">The
answer to the question “What will change?” for the recycling system
is a common one: more types will be added to the system. The goal of the
design, then, is to make this addition of types as painless as possible. In the
recycling program, we’d like to encapsulate all places where specific
type information is mentioned, so (if for no other reason) any changes can be
localized to those encapsulations. It turns out that this process also cleans
up the rest of the code considerably.
</FONT><a name="_Toc375545415"></a><a name="_Toc408018800"></a><P></DIV>
<A NAME="Heading558"></A><H3 ALIGN=LEFT>
“Make
more objects”
</H3>
<DIV ALIGN=LEFT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">This
brings up a general object-oriented design principle that I first heard spoken
by <A NAME="Index2951"></A>Grady
Booch: “If the design is too complicated, make more objects.” This
is simultaneously counterintuitive and ludicrously simple, and yet it’s
the most useful guideline I’ve found. (You might observe that
“making more objects” is often equivalent to “add another
level of indirection.”) In general, if you find a place with messy code,
consider what sort of class would clean that up. Often the side effect of
cleaning up the code will be a system that has better structure and is more
flexible.
</FONT><P></DIV><DIV ALIGN=LEFT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">Consider
first the place where
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>Trash</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
objects are created, which is a
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>switch</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
statement inside
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>main( )</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">:</FONT><P></DIV>
<font color="#990000"><PRE> <font color="#0000ff">for</font>(<font color="#0000ff">int</font> i = 0; i < 30; i++)
<font color="#0000ff">switch</font>((<font color="#0000ff">int</font>)(Math.random() * 3)) {
<font color="#0000ff">case</font> 0 :
bin.addElement(<font color="#0000ff">new</font>
Aluminum(Math.random() * 100));
<font color="#0000ff">break</font>;
<font color="#0000ff">case</font> 1 :
bin.addElement(<font color="#0000ff">new</font>
Paper(Math.random() * 100));
<font color="#0000ff">break</font>;
<font color="#0000ff">case</font> 2 :
bin.addElement(<font color="#0000ff">new</font>
Glass(Math.random() * 100));
} </PRE></font><DIV ALIGN=LEFT><P></DIV><DIV ALIGN=LEFT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">This
is definitely messy, and also a place where you must change code whenever a new
type is added. If new types are commonly added, a better solution is a single
method that takes all of the necessary information and produces a handle to an
object of the correct type, already upcast to a trash object. In
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><I>Design
Patterns
</I></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
this is broadly referred to as a <A NAME="Index2952"></A><A NAME="Index2953"></A></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><I>creational
pattern
</I></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
(of which there are several). The specific pattern that will be applied here is
a variant of the <A NAME="Index2954"></A><A NAME="Index2955"></A></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><I>Factory
Method
</I></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">.
Here, the factory method is a
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>static</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
member of
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>Trash</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">,
but more commonly it is a method that is overridden in the derived class.
</FONT><P></DIV><DIV ALIGN=LEFT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">The
idea of the factory method is that you pass it the essential information it
needs to know to create your object, then stand back and wait for the handle
(already upcast to the base type) to pop out as the return value. From then on,
you treat the object polymorphically. Thus, you never even need to know the
exact type of object that’s created. In fact, the factory method hides it
from you to prevent accidental misuse. If you want to use the object without
polymorphism, you must explicitly use RTTI and casting.
</FONT><P></DIV><DIV ALIGN=LEFT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">But
there’s a little problem, especially when you use the more complicated
approach (not shown here) of making the factory method in the base class and
overriding it in the derived classes. What if the information required in the
derived class requires more or different arguments? “Creating more
objects” solves this problem. To implement the factory method, the
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>Trash</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
class gets a new method called
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>factory</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">.
To hide the creational data, there’s a new class called
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>Info</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
that contains all of the necessary information for the
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>factory</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
method to create the appropriate
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>Trash</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
object. Here’s a simple implementation of
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>Info</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">:</FONT><P></DIV>
<font color="#990000"><PRE><font color="#0000ff">class</font> Info {
<font color="#0000ff">int</font> type;
<font color="#009900">// Must change this to add another type:</font>
<font color="#0000ff">static</font> <font color="#0000ff">final</font> <font color="#0000ff">int</font> MAX_NUM = 4;
<font color="#0000ff">double</font> data;
Info(<font color="#0000ff">int</font> typeNum, <font color="#0000ff">double</font> dat) {
type = typeNum % MAX_NUM;
data = dat;
}
}</PRE></font><DIV ALIGN=LEFT><P></DIV><DIV ALIGN=LEFT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">An
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>Info</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
object’s only job is to hold information for the
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>factory( )</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
method. Now, if there’s a situation in which
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>factory( )</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
needs more or different information to create a new type of
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>Trash</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
object, the
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>factory( )</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
interface doesn’t need to be changed. The
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>Info</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
class can be changed by adding new data and new constructors, or in the more
typical object-oriented fashion of subclassing.
</FONT><P></DIV><DIV ALIGN=LEFT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">The
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>factory( )</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
method for this simple example looks like this:
</FONT><P></DIV>
<font color="#990000"><PRE> <font color="#0000ff">static</font> Trash factory(Info i) {
<font color="#0000ff">switch</font>(i.type) {
<font color="#0000ff">default</font>: <font color="#009900">// To quiet the compiler</font>
<font color="#0000ff">case</font> 0:
<font color="#0000ff">return</font> <font color="#0000ff">new</font> Aluminum(i.data);
<font color="#0000ff">case</font> 1:
<font color="#0000ff">return</font> <font color="#0000ff">new</font> Paper(i.data);
<font color="#0000ff">case</font> 2:
<font color="#0000ff">return</font> <font color="#0000ff">new</font> Glass(i.data);
<font color="#009900">// Two lines here:</font>
<font color="#0000ff">case</font> 3:
<font color="#0000ff">return</font> <font color="#0000ff">new</font> Cardboard(i.data);
}
} </PRE></font><DIV ALIGN=LEFT><P></DIV><DIV ALIGN=LEFT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">Here,
the determination of the exact type of object is simple, but you can imagine a
more complicated system in which
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>factory( )</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
uses an elaborate algorithm. The point is that it’s now hidden away in
one place, and you know to come to this place when you add new types.
</FONT><P></DIV><DIV ALIGN=LEFT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">The
creation of new objects is now much simpler in
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>main( )</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">:</FONT><P></DIV>
<font color="#990000"><PRE> <font color="#0000ff">for</font>(<font color="#0000ff">int</font> i = 0; i < 30; i++)
bin.addElement(
Trash.factory(
<font color="#0000ff">new</font> Info(
(<font color="#0000ff">int</font>)(Math.random() * Info.MAX_NUM),
Math.random() * 100))); </PRE></font><DIV ALIGN=LEFT><P></DIV><DIV ALIGN=LEFT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">An
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>Info</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
object is created to pass the data into
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>factory( )</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">,
which in turn produces some kind of
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>Trash</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
object on the heap and returns the handle that’s added to the
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>Vector</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>bin</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">.
Of course, if you change the quantity and type of argument, this statement will
still need to be modified, but that can be eliminated if the creation of the
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>Info</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
object is automated. For example, a
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>Vector</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
of arguments can be passed into the constructor of an
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>Info</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
object (or directly into a
</FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black"><B>factory( )</B></FONT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">
call, for that matter). This requires that the arguments be parsed and checked
at runtime, but it does provide the greatest flexibility.
</FONT><P></DIV><DIV ALIGN=LEFT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">You
can see from this code what “<A NAME="Index2956"></A><A NAME="Index2957"></A><A NAME="Index2958"></A>vector
of change” problem the factory is responsible for solving: if you add new
types to the system (the change), the only code that must be modified is within
the factory, so the factory isolates the effect of that change.
</FONT><a name="_Toc375545418"></a><a name="_Toc408018801"></a><P></DIV>
<A NAME="Heading559"></A><H3 ALIGN=LEFT>
A
pattern for prototyping creation
</H3>
<DIV ALIGN=LEFT><FONT FACE="Carmina Md BT" SIZE=3 COLOR="Black">A
problem with the design above is that it still requires a central location
where all the types of the objects must be known: inside the
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -