适配器模式

适配器模式被设计用于与已有代码进行交互。适配器适用于允许两个已存在的对象在一起工作,即使他们的接口不兼容。就像允许USB键盘出入PS/2端口一样,适配对象位于两个不同接口之间,对两个接口进行即时转换。适配器对象的唯一工作就是执行转换,转换可能涉及很多任务,比如将参数转换成另一种格式,重新排列参数的顺序,调用名称不同的方法或提供默认参数。

在结构上,适配器模式类似于一个简化的装饰器模式。装饰器模式通常提供它们所替代的相同接口,而适配器模式在两个不同的接口之间进行匹配。下图是适配器在UML中的表现形式:

这里,接口1期望调用一个称为make_action(some, args)的方法。我们已经有个很完美的接口2能做到我们想要的一切(并且为了避免重复,我不想重写他们),但是提供的方法被命名为diffe_action(other, args)。适配器类将会实现make_action接口并将参数匹配到已有的接口。

这里的优势在于,代码一次性的对一个接口到其他所有借口进行了全部匹配。另一种方法则使得我们每次访问这个代码时,都需要在多个不同的地方对其进行直接转换。

适配器模式例子

假设我们有以下的类,以格式'YYYY-MM-DD'接收一个字符串的日期并通过日期计算一个人的年龄:

class AgeCaculator(object):
    def __init__(self, birthday):
        self.year, self.month, self.day = (int(x) for x in birthday.split('-'))
    
    def calculate_age(self, date):
        year, month, day = (int(x) for x in data.split('-'))
        age = year - self.year
        if (month, day) < (self.month, self.day):
            age -= 1
        return age 

这是一个非常简单的类,可以一眼看出他在做什么。但是,我们必须考虑一下程序员的想法,这里使用了一个特定格式的字符串,而不是python中十分有用的内置datetime库。我们编写的大多数程序都将要与datetime对象,而不是字符串交互。

我们有多种方法来解决这种问题,我们可以重写这个类来接受datetime对象,这将可能会更加准确。但是,如果这个类是由第三方提供的,我们不知道他的内部是什么,或者我们根本就不允许去更改他们,我们就需要尝试一下别的东西。我们可以按照他原本的模样来使用这个类,每当我们要通过datetime.date对象来计算年龄时,我们可以调用datetime.date.strftime('%Y-%M-%d')函数将其转换为适当的格式。但是转换发生在很多地方,那么我们就需要在那么多地方都进行更改,而且一旦发生错误,我们还需要一个个的改回去。这是非常难以进行维护的,他违反了DRY(Do Not Repeat Yourself)原则。

或者,我们可以编写一个类适配器,运行正常的日期插入到一个普通的AgeCalculator类中:

import datetime

class DateAgeAdapter(object):
    def _str_date(self, date):
        return date.strftime("%Y-%m-%d")
    
    def __init__(self, birthday):
        birthday = self._str_date(birthday)
        self.calculator = AgeCaculator(birthday)
    
    def get_age(self, date):
        date = self._str_date(date)
        return self.calculator.calculate_age(date)

该适配器将datetime.datedatetime.time(他们对函数strftime有相同的接口)装换成我们普通的AgeCalculator可以使用的字符串。现在我们可以在新接口上使用原来的代码。我们将方法名改成了get_age以证明调用接口也可能在寻找不同的方法名,而不仅仅是一个不同类型的参数。

创建一个类作为适配器是实现一种模式的常用方式,但是,像往常一样,还有许多其他的方法可以做到这一点。继承和多重继承可以用于将功能添加到一个类中。例如,我们可以从date类中添加适配器,这样他的工作原理就与原来的AgeCaculator相同。

import datetime

class AgeableDate(datetime.date):
    def split(self, char):
        return self.year, self.month, self.day

在这里我们创建了一个继承自datetime.date的类,并添加了一个split方法,该方法接收一个参数(我们将其忽略掉),并返回一个年月日的元组。这与我们的AgeCalculator能够完美结合,因为该代码是一种特殊的格式调用strip函数,而在这种情况下,strip会返回一个年月日的元组。这个AgeCalculator只在意strip函数是否存在,并返回可接收的值;它不在意我们是否真的在传递一个字符串。但他确实有效。

bd = AgeableDate(1975, 6, 14)
bd
today = AgeableDate.today()
today
a = AgeCaculator(bd)
a.calculate_age(today)

# 输出:
AgeableDate(1975, 6, 14)
AgeableDate(2019, 4, 14)
43

但是这个使用继承替代的适配器常常会难以理解和维护。而使用适配器来替代继承,目的则会更加的明显。

我们也可以将函数当成适配器,这并不完全符合标准的适配器模式,但在通常情况下,你可以简单的传递数据到一个函数,然后将它以一个适当的形式返回作为进入进入另一个接口的入口。

参考:
《Python3 面向对象编程》

你可能感兴趣的:(适配器模式)