在面向对象的编程中,经常用到的一种方式就是将更简单的对象组合形成复杂的对象,而复杂的对象还可以组合形成更复杂的对象。这也正是现实世界存在的一种方式,如一台电风扇,可以由底座、支架、控制器、扇头等组成,扇头又有网罩、扇叶、电机组成,电机又由轴承、转子、线圈等组成。编程实现的话,就可以把各种元件建模为对象,然后逐步组合即可。
下面以要实现的图形化时钟的组成,来看类的组合关系。时钟的组成部件可以简单看作为表盘(带有刻度和外周圆圈)、时针、分针和秒针组成,前面定义了刻度类,下面就是表盘外周圆圈类和表盘类的代码。
class PlateOuter:
def __init__(self,canvas,center_point,radius):
self.canvas = canvas
self.center_point = center_point
self.radius = radius
self.widget_id = None
def draw(self):
x0 = self.center_point[0] - self.radius
y0 = self.center_point[1] - self.radius
x1 = self.center_point[0] + self.radius
y1 = self.center_point[1] + self.radius
self.canvas.create_oval(x0,y0,x1,y1)
def delete(self):
if self.widget_id:
self.canvas.delete(self.widget_id)
class Plate:
def __init__(self,canvas, center_point, radius, plong):
self.canvas = canvas
self.center_point = center_point
self.radius = radius
self.plong = plong
self.markers = []
self.gen_markers()
def draw(self):
for marker in self.markers:
marker.draw()
def gen_markers(self):
for start,end in zip(gen_end_points(self.center_point,self.radius,6),
gen_end_points(self.center_point,self.radius + self.plong,6)):
self.markers.append(Marker(start[1],end[1],self.canvas))
self.markers.append(PlateOuter(self.canvas,self.center_point,self.radius+20))
def delete(self):
for w in self.markers:
self.canvas.delete(w)
PlateOuter类就是用来代表个表盘外圆圈的,其基本结构也很简单,类似之前的Marker等类,包括四个实例变量和三个实例方法,四个实例变量分别用来表示画布、表盘的中心点座标、表盘的半径和表盘圆的部件id;三个实例方法,第一个是构造方法,draw()方法是依据表盘的中心座标和半径计算后在指定的位置绘制一个圆,delete()方法是用于清除自己的。
在Plate类中,组合了PlateOuter类和多个Marker类构成了表盘,在gen_markers()方法中,生成了表示每个刻度的刻度类的实例和一个PlateOuter类的实例;在draw()方法中,只不过是调用每个组成部分的draw()方法来进行绘制就可以了,delete()方法中也是采取同样的方式来实现的。
下面来看看时钟代码:
class MyClocker:
def __init__(self,root,canvas, center_point, radius, plong):
self.root = root
self.plate = Plate(canvas, center_point, radius, plong)
self.s_pointer = Pointer(6,canvas,center_point,plong=180,width=1,color='red')
self.m_pointer = Pointer(3,canvas,center_point,plong=150,width=2,color='blue')
self.h_pointer = Pointer(1,canvas,center_point,plong=120,width=4,color='black')
self.display()
def display(self):
self.plate.draw()
self.s_pointer.draw()
self.m_pointer.draw()
self.h_pointer.draw()
self.root.update()
def walk(self):
self.s_pointer.walk()
if (self.s_pointer.count + 1) % 30 == 0:
self.m_pointer.walk()
if (self.m_pointer.count + 1) % 4 == 0:
self.h_pointer.walk()
def start(self):
while True:
self.walk()
self.root.update()
time.sleep(1)
在时钟代码类MyClocker中,第一个仍然是构造方法,其中的主要部分为实例化一个表示表盘类Plate,实例化三个Pointer类分别代表时针、分针和秒针,之后调用display()方法绘制各个组成部件;walk()方法中,秒针移动一步,之后检测分针是否应该移动一步,若分针移动一步,则之后还会检测时针是否应该移动一步。
start()方法是时钟启动的关键方法,在其中使用了一个while死循环,调用一次walk()方法后,就sleep一秒。这样就达到每秒移动秒针一步,其他指针会按规则移动的,整个时钟就可以正确无误的工作了。
如果你要知道时钟的图形界面是如何生成的,就必须要了解一下tkinter库。其主要调用代码如下:
root = tkinter.Tk()
cvns = tkinter.Canvas(root,width=530,height=530,bg='white')
cvns.pack()
mc = MyClocker(root,cvns,(260,260),200,10)
t = threading.Thread(target=mc.start)
t.setDaemon(True)
t.start()
root.resizable(False, False)
root.mainloop()
注意这里使用了一个线程来启动时钟的指针的转动,可以避免在进行其他图形化操作时,指针停止的问题。