📄 sect12.htm
字号:
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>[[ NOTE: this example has <font color=#0000ff>not</font> been converted. See further down <font color=#0000ff>for</font> a version that has the GUI but <font color=#0000ff>not</font> the Observers, <font color=#0000ff>in</font> PythonCard. ]]
# c10:BoxObserver.py
# Demonstration of Observer pattern using
# Java's built-<font color=#0000ff>in</font> observer classes.
# You must inherit a type of Observable:
<font color=#0000ff>class</font> BoxObservable(Observable):
<font color=#0000ff>def</font> notifyObservers(self, Object b):
# Otherwise it won't propagate changes:
setChanged()
super.notifyObservers(b)
<font color=#0000ff>class</font> BoxObserver(JFrame):
Observable notifier = BoxObservable()
<font color=#0000ff>def</font> __init__(self, int grid):
setTitle(<font color=#004488>"Demonstrates Observer pattern"</font>)
Container cp = getContentPane()
cp.setLayout(GridLayout(grid, grid))
<font color=#0000ff>for</font>(int x = 0 x < grid x++)
<font color=#0000ff>for</font>(int y = 0 y < grid y++)
cp.add(OCBox(x, y, notifier))
<font color=#0000ff>def</font> main(self, String[] args):
int grid = 8
<font color=#0000ff>if</font>(args.length > 0)
grid = Integer.parseInt(args[0])
JFrame f = BoxObserver(grid)
f.setSize(500, 400)
f.setVisible(1)
# JDK 1.3:
f.setDefaultCloseOperation(EXIT_ON_CLOSE)
# Add a WindowAdapter <font color=#0000ff>if</font> you have JDK 1.2
<font color=#0000ff>class</font> OCBox(JPanel) implements Observer:
Observable notifier
int x, y # Locations <font color=#0000ff>in</font> grid
Color cColor = newColor()
static final Color[] colors =:
Color.black, Color.blue, Color.cyan,
Color.darkGray, Color.gray, Color.green,
Color.lightGray, Color.magenta,
Color.orange, Color.pink, Color.red,
Color.white, Color.yellow
static final Color newColor():
<font color=#0000ff>return</font> colors[
(int)(Math.random() * colors.length)
]
<font color=#0000ff>def</font> __init__(self, int x, int y, Observable notifier):
self.x = x
self.y = y
notifier.addObserver(self)
self.notifier = notifier
addMouseListener(ML())
<font color=#0000ff>def</font> paintComponent(self, Graphics g):
super.paintComponent(g)
g.setColor(cColor)
Dimension s = getSize()
g.fillRect(0, 0, s.width, s.height)
<font color=#0000ff>class</font> ML(MouseAdapter):
<font color=#0000ff>def</font> mousePressed(self, MouseEvent e):
notifier.notifyObservers(OCBox.self)
<font color=#0000ff>def</font> update(self, Observable o, Object arg):
OCBox clicked = (OCBox)arg
<font color=#0000ff>if</font>(nextTo(clicked)):
cColor = clicked.cColor
repaint()
private final boolean nextTo(OCBox b):
<font color=#0000ff>return</font> Math.abs(x - b.x) <= 1 &&
Math.abs(y - b.y) <= 1
# :~
</PRE></FONT></BLOCKQUOTE><DIV ALIGN="LEFT"><P><FONT FACE="Georgia">When you first look at the online
documentation for <B>Observable</B>, it’s a bit confusing because it
appears that you can use an ordinary <B>Observable</B> object to manage the
updates. But this doesn’t work; try it—inside <B>BoxObserver</B>,
create an <B>Observable</B> object instead of a <B>BoxObservable</B> object and
see what happens: nothing. To get an effect, you <I>must</I> inherit from
<B>Observable</B> and somewhere in your derived-class code call
<A NAME="Index25"></A><B>setChanged( )</B>. This is the method that sets
the “changed” flag, which means that when you call
<A NAME="Index26"></A><B>notifyObservers( )</B> all of the observers will,
in fact, get notified. In the example above <B>setChanged( )</B> is simply
called within <B>notifyObservers( )</B>, but you could use any criterion
you want to decide when to call <B>setChanged( )</B>.
<A HREF="http://www.mindview.net/Books/TIPython/BackTalk/FindPage/A_317">Add Comment</A></FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia"><B>BoxObserver</B> contains a single
<B>Observable </B>object called <B>notifier</B>, and every time an <B>OCBox</B>
object is created, it is tied to <B>notifier</B>. In <B>OCBox</B>, whenever you
click the mouse the <B>notifyObservers( )</B> method is called, passing the
clicked object in as an argument so that all the boxes receiving the message (in
their <B>update( ) </B>method) know who was clicked and can decide whether
to change themselves or not. Using a combination of code in
<B>notifyObservers( )</B> and <B>update( )</B> you can work out some
fairly complex schemes.
<A HREF="http://www.mindview.net/Books/TIPython/BackTalk/FindPage/A_318">Add Comment</A></FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">It might appear that the way the
observers are notified must be frozen at compile time in the
<B>notifyObservers( )</B> method. However, if you look more closely at the
code above you’ll see that the only place in <B>BoxObserver</B> or
<B>OCBox</B> where you're aware that you’re working with a
<B>BoxObservable</B> is at the point of creation of the <B>Observable
</B>object—from then on everything uses the basic <B>Observable</B>
interface. This means that you could inherit other <B>Observable</B> classes and
swap them at run time if you want to change notification behavior then.
<A HREF="http://www.mindview.net/Books/TIPython/BackTalk/FindPage/A_319">Add Comment</A></FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Here is a version of the above that
doesn’t use the Observer pattern, written by Kevin Altis using PythonCard,
and placed here as a starting point for a translation that does include
Observer:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>#: c10:BoxObserver.py
<font color=#004488>""</font>" Written by Kevin Altis as a first-cut <font color=#0000ff>for</font>
converting BoxObserver to Python. The Observer
hasn't been integrated yet.
To run this program, you must:
Install WxPython <font color=#0000ff>from</font>
http:<font color=#009900>//www.wxpython.org/download.php</font>
Install PythonCard. See:
http:<font color=#009900>//pythoncard.sourceforge.net</font>
<font color=#004488>""</font>"
<font color=#0000ff>from</font> PythonCardPrototype <font color=#0000ff>import</font> log, model
<font color=#0000ff>import</font> random
GRID = 8
<font color=#0000ff>class</font> ColorBoxesTest(model.Background):
<font color=#0000ff>def</font> on_openBackground(self, target, event):
self.document = []
<font color=#0000ff>for</font> row <font color=#0000ff>in</font> range(GRID):
line = []
<font color=#0000ff>for</font> column <font color=#0000ff>in</font> range(GRID):
line.append(self.createBox(row, column))
self.document.append(line[:])
<font color=#0000ff>def</font> createBox(self, row, column):
colors = ['black', 'blue', 'cyan',
'darkGray', 'gray', 'green',
'lightGray', 'magenta',
'orange', 'pink', 'red',
'white', 'yellow']
width, height = self.panel.GetSizeTuple()
boxWidth = width / GRID
boxHeight = height / GRID
log.info(<font color=#004488>"width:"</font> + str(width) +
<font color=#004488>" height:"</font> + str(height))
log.info(<font color=#004488>"boxWidth:"</font> + str(boxWidth) +
<font color=#004488>" boxHeight:"</font> + str(boxHeight))
# use an empty image, though some other
# widgets would work just as well
boxDesc = {'type':'Image',
'size':(boxWidth, boxHeight), 'file':''}
name = 'box-%d-%d' % (row, column)
# There <font color=#0000ff>is</font> probably a 1 off error <font color=#0000ff>in</font> the
# calculation below since the boxes should
# probably have a slightly different offset
# to prevent overlaps
boxDesc['position'] =
(column * boxWidth, row * boxHeight)
boxDesc['name'] = name
boxDesc['backgroundColor'] =
random.choice(colors)
self.components[name] = boxDesc
<font color=#0000ff>return</font> self.components[name]
<font color=#0000ff>def</font> changeNeighbors(self, row, column, color):
# This algorithm will result <font color=#0000ff>in</font> changing the
# color of some boxes more than once, so an
# OOP solution where only neighbors are asked
# to change <font color=#0000ff>or</font> boxes check to see <font color=#0000ff>if</font> they are
# neighbors before changing would be better
# per the original example does the whole grid
# need to change its state at once like <font color=#0000ff>in</font> a
# Life program? should the color change
# <font color=#0000ff>in</font> the propogation of another notification
# event?
<font color=#0000ff>for</font> r <font color=#0000ff>in</font> range(max(0, row - 1),
min(GRID, row + 2)):
<font color=#0000ff>for</font> c <font color=#0000ff>in</font> range(max(0, column - 1),
min(GRID, column + 2)):
self.document[r][c].backgroundColor=color
# this <font color=#0000ff>is</font> a background handler, so it isn't
# specific to a single widget. Image widgets
# don't have a mouseClick event (wxCommandEvent
# <font color=#0000ff>in</font> wxPython)
<font color=#0000ff>def</font> on_mouseUp(self, target, event):
prefix, row, column = target.name.split('-')
self.changeNeighbors(int(row), int(column),
target.backgroundColor)
<font color=#0000ff>if</font> __name__ == '__main__':
app = model.PythonCardApp(ColorBoxesTest)
app.MainLoop()
#:~</PRE></FONT></BLOCKQUOTE><DIV ALIGN="LEFT"><P><FONT FACE="Georgia">This is the resource file for
running the program (see PythonCard for details):</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>#: c10:BoxObserver.rsrc.py
{'stack':{'type':'Stack',
'name':'BoxObserver',
'backgrounds': [
{ 'type':'Background',
'name':'bgBoxObserver',
'title':'Demonstrates Observer pattern',
'position':(5, 5),
'size':(500, 400),
'components': [
] # end components
} # end background
] # end backgrounds
} }
#:~<A NAME=<font color=#004488>"_Toc534420127"</font>></A></PRE></FONT></BLOCKQUOTE><A NAME="Heading78"></A><FONT FACE = "Verdana, Tahoma, Arial, Helvetica, Sans"><H2 ALIGN="LEFT">
Exercises</H2></FONT>
<OL>
<LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Using the approach in
<B>Synchronization.py</B>, create a tool that will automatically wrap all the
methods in a class to provide an execution trace, so that you can see the name
of the method and when it is entered and exited.
<A HREF="http://www.mindview.net/Books/TIPython/BackTalk/FindPage/A_320">Add Comment</A></FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Create
a minimal Observer-Observable design in two classes. Just create the bare
minimum in the two classes, then demonstrate your design by creating one
<B>Observable</B> and many <B>Observer</B>s, and cause the <B>Observable</B> to
update the <B>Observer</B>s.
<A HREF="http://www.mindview.net/Books/TIPython/BackTalk/FindPage/A_321">Add Comment</A></FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Modify
<B>BoxObserver.py</B> to turn it into a simple game. If any of the squares
surrounding the one you clicked is part of a contiguous patch of the same color,
then all the squares in that patch are changed to the color you clicked on. You
can configure the game for competition between players or to keep track of the
number of clicks that a single player uses to turn the field into a single
color. You may also want to restrict a player's color to the first one that was
chosen.
<A HREF="http://www.mindview.net/Books/TIPython/BackTalk/FindPage/A_322">Add Comment</A></FONT></OL>
<DIV ALIGN="CENTER">
<FONT FACE="Verdana, Tahoma, Arial, Helvetica, Sans" size = "-1">
[ <a href="Sect11.htm">Previous Chapter</a> ]
[ <a href="javascript:window.location.href = 'Index.htm';">Table of Contents</a> ]
[ <a href="DocIdx.htm">Index</a> ]
[ <a href="Sect13.htm">Next Chapter</a> ]
</FONT>
<BR>
Last Update:12/31/2001</P></DIV>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -