策略模式是一种常见的面向对象编程的抽象模式。针对同一问题,这个模式实现了不同对象采用不同解决方法。客户端代码可以在运行时动态选择其中最恰当的实现。
一般来说,不同的算法各有可取之处,一种可能会比另一种速度快但会占用更多的内存,而当多CPU或分布式系统存在时,我们或许能发现更合适的第三种算法。下图是策略模式在UML中的表现形式:连接到策略模式的用户代码仅仅需要知道和它打交道的抽象接口。对同一任务的实际实现可能会选择完全不同的方法,但是无论采用何种方法,接口都是完全相同的。
策略模式实例
策略模式的一个典型例子是排序:这些年来,大量的算法被发明出来用于对一个对象的集合进行排序。快速排序,归并排序和堆排序都是具有不同特性的快速排序算法,算法本身的选择取决于内容的大小,类型,以及系统的需要。
如果我们的客户端代码需要对一个集合进行排序,我们可以将它传递给一个拥有sort方法的对象。这个对象可以是一个QuickSorter或MergeSorter对象;两种情况的结果都会相同:一个有序的列表。用来排序的策略是从调用代码中抽象出来的,这使其可以模块化并且可替换。
当然,在Python中,我们通常仅仅调用sorted
函数或list.sort
函数,并且相信它们能以一个接近最优的方式来进行排序。所以我们需要看一个更好的实例。
以桌面墙纸管理软件为例。首先,策略模式可以用于从硬盘加载不同格式的图像(JPEG、GIF、PNG、TIFF),然后显示它们。这里已经有一些库可以帮助我们处理透明度的问题。因此,让我们在想多一些。当图像被显示到桌面背景时,它能够以不同的方式适应屏幕大小。例如,假设图像比屏幕小,那他可以平铺到整个屏幕上、居中或是压缩放至合适的大小。同样这里还存在其他更复杂的策略,例如扩展到最大的高度或宽度,将他与固态、半透明或渐变的背景色结合,以及其他操作。我们稍后或许会添加这些策略,现在让我们从最基本的开始。
我们的策略对象接受两个输入:将要显示的图像以及一个包含屏幕宽度和高度的元组。它们将根据屏幕大小使用指定的策略处理图像,并返回一个新的图像。
from pygame import image
from pygame.transform import scale
from pygame import Surface
class TiledStrategy(object):
def make_background(self, img_file, desktop_size):
in_img = image.load(img_file)
out_img = Surface(desktop_size)
for x in range((out_img.get_width() // in_img.get_width()) + 1):
for y in range((out_img.get_height() // in_img.get_height()) + 1):
out_img.blit(in_img,
(in_img.get_width() * x, in_img.get_height() * y))
return out_img
class CenteredStrategy(object):
def make_background(self, img_file, desktop_size):
in_img = image.load(img_file)
out_img = Surface(desktop_size)
out_img.fill((0, 0, 0))
left = (out_img.get_width() - in_img.get_width()) / 2
top = (out_img.get_height() - in_img.get_height()) / 2
out_img.blit(in_img, (left, top))
return out_img
class ScaledStrategy(object):
def make_background(self, img_file, desktop_size):
in_img = image.load(img_file)
return scale(in_img, desktop_size)
这里我们有三种策略,每种都是用pygame来执行它们的任务。每个策略都包含一个make_background
方法来接受同样的一组参数。一旦被选定,就会调用适当的策略来创建一个合适大小的桌面背景。TiledStrategy
根据输入图像的高度和宽度进行循环并将其重复叠加到新图像的适当位置。CenteredStrategy
计算出需要留出多少空间给4个边缘来使图像居中。ScaledStrategy
只是简单的将图像调整成输出需要的设计(忽略长宽比)。
若不是用策略模式来实现这些选项之间的切换。我们需要将所有的代码放到一个巨大的方法中,并且使用很尴尬的if语句去选择。而每次当我们想添加一个新的策略时,我们就不得不使这个函数变得更加笨重。
Python中的策略模式
上面谈论的策略模式的规范实现在大多数面向对象函数库中被非常普遍的运用,但是在Python中却很少见。因为这些类对象仅仅提供了一个函数。我们可以轻松的调用__call__
函数使对象可以直接被调用。但是由于这些对象没有和任何数据关联,我们实际上可以创建一组顶级函数并传递它们来替代。
设计模式的反对者可能会因此认为策略模式在Python中是没有必要的。事实上,Python的一级函数允许我们用更直接的方式来实现这一策略模式。熟知策略模式的存在仍然可以帮助我们为我们的程序选择正确的设计,我们只是使用了不同的简单方法来实现它。当我们需要允许客户端或终端用户在多个具有相同接口的实现方式之间进行选择时,策略模式或某个可以实现它的顶级函数就应当被使用。
参考:
《Python3 面向对象编程》
网友评论