python如何设计公共类

来源:互联网 发布:java贪吃蛇文档 编辑:程序博客网 时间:2024/05/18 20:36

    python是一门很优秀的语言,也是一门面向对象的语言。在面向对象中,我们无法避免的就是一个优秀类的设计。今天,我就想借着python对时间类的简单的设计,来探讨一个优秀的类应该如何去设计,在函数的拟定过程中我们需要考虑到什么问题,以及当当前的语言对某些功能不支持的时候,我们又是如何转弯的去实现,比如python不支持构造函数的重载操作。

    python中和时间相关的处理类有很多也比较繁杂,初学者容易被各种各样的时间操作搞的头晕眼花,再加上python的版本不兼容问题,很容易让人在这里花上较多的时间来记忆和学习。比如datetimetimedate这些类,每个类支持的功能大同小异,但又不尽相同,再加上版本问题,同一个类的不同版本支持的函数也不一样,就更加让python的时间操作变得繁琐。下面我就用python来实现一个简单的时间类,来说明一个优秀的公共类的设计规则。

   准备工作,我们先定义一个python中没有的操作符——三元操作符的函数:

# function: conditions ? a : bdef ThreeOperator(conditions, a, b):    return (conditions and [a] or [b])[0]

       接下来,我们开始定义时间类,DateTimeUtil,下面是类的初始定义(__init__()):

class DateTimeUtil:    # the init function    # the flag value is in 'STRING', 'DATETIME', 'VALUES', 'TIMESTRUCT'    def __init__(self, one, two = '', three = '',                 four = '', five = '', six = '', seven = ''):        self.__value = self.__factory(one)(one, two, three, four, five, six, seven)

   在上述的构造函数中,我们调用了__factory()方法,暂且成为工厂,它主要负责将各种不同类型不同个数的参数分配到具体的函数进行处理,它是实现构造函数重载的核心思想。下面是该函数的具体实现方式,注意,我们使用了很多的lambda操作来辅助我们完成具体的任务。下面是该函数的具体实现过程:

    def __factory(self, flag):        if isinstance(flag, str):            return lambda dStr, dFormat, *more: self.__dealWithString(dStr,                                                                      ThreeOperator(dFormat, dFormat, "%Y%m%d"),)        elif isinstance(flag, time.struct_time):            return lambda timeStruct, *more: self.__dealWithTimeStruct(timeStruct)        elif isinstance(flag, (int, float)):            return lambda year, month, day, hour, minute, second, millisecond: \                self.__dealWithValues(year, ThreeOperator(isinstance(month, int), month, 1),                                      ThreeOperator(isinstance(day, int), day, 1),                                      ThreeOperator(isinstance(hour, int), hour, 0),                                      ThreeOperator(isinstance(minute, int), minute, 0),                                      ThreeOperator(isinstance(second, int), second, 0),                                      ThreeOperator(isinstance(millisecond, int), millisecond, 0),)        elif isinstance(flag, datetime.datetime):            return lambda dtime, *more: self.__dealWithDateTime(dtime)

    __factory()方法中,有很多以 __deal开头的函数,这些函数针对具体类型处理的函数。比如说__dealWithValues() 这个函数,就针对传进来的年月日分散值进行处理的函数,这个函数有一个比较人性化的考虑,就是如果day传递进来的是 -1 的话,就认为是该月份的最后一天。对函数的__dealWithValues实现如下: 

    def __dealWithValues(self, year, month = 1, day = 1,                         hour = 0, minute = 0, second = 0,                         milliseconds = 0):        try:            maxDay = calendar.monthrange(year, month)[1]            if day < 0:                day = maxDay + 1 + day            return datetime.datetime(year, month, day, hour,                                     minute, second, milliseconds)        except Exception:            raise

   好了我们的第一步的目标已经实现,也就是构造函数的重载。完成了构造函数实现,可以说我们类的入口已经确定下来了,开门的大小、种类丰富会让我们的参观者(使用者)更加方便的进入到我们类的核心,也就是各种功能函数的使用。如果我们把构造函数比作一栋建筑的门,那么函数就可以比作成建筑的架构了,优秀、方便的函数设计能让我们的建筑经得起不同使用者的考验,让不同类型的使用者都能领略到这栋建筑的美以及坚固。接下来,我们讨论几个核心函数的设计过程。

   对时间最常见的操作,除了时间的查看外就是时间的加减操作了。所以我们首先考虑了定义时间的加减操作——dayDelta函数,来提供一个具体的时间,加年、月、日、周等具体的操作。该函数的提供了以下几个参数:days = 0, seconds = 0,microseconds = 0,milliseconds = 0, minutes = 0,hours = 0,weeks = 0, months = 0, years = 0,参数的意思都很简洁明白。下面就是该函数的具体代码实现: 

    def dayDelta(self, days = 0, seconds = 0, microseconds = 0,                 milliseconds = 0, minutes = 0, hours = 0,                 weeks = 0, months = 0, years = 0):        # year, month, day        newYear, newMonth, newDay = self.__value.year, self.__value.month, self.__value.day        if years != 0 or months != 0:            totalMonths = (self.__value.year + years) * 12 + \                          (self.__value.month + months) - 1            newYear = int(totalMonths / 12)            newMonth = totalMonths % 12 + 1            newMaxDay = calendar.monthrange(newYear, newMonth)[1]            newDay = ThreeOperator(self.__value.day > newMaxDay, newMaxDay, self.__value.day)        self.__value = datetime.datetime(newYear, newMonth, newDay, self.__value.hour,                                         self.__value.minute, self.__value.second,                                         self.__value.microsecond)        newDate = self.__value + datetime.timedelta(days, seconds, microseconds,                                                    milliseconds, minutes, hours, weeks)        self.__init__(newDate)        return self

   我们已经实现了 dayDelta的函数功能,但是这个函数有两个缺点,第一个就是非静态函数,不创建对象,就无法使用该函数,第二个就是它会修改对象的值,不符合一些场景下的需求。面对以上的两个缺点,我们就必须提供另外一个函数类似的函数,dayDelta_s,所以这个函数必须是类函数而且是不能修改穿进去参数的值,并返回新的值。 

    @classmethod    def dayDelta_s(self, orignalDate, days = 0, seconds = 0, microseconds = 0,                   milliseconds = 0, minutes = 0, hours = 0,                   weeks = 0, months = 0, years = 0):        orignalDateCopy = orignalDate.copy()        return orignalDateCopy.dayDelta(days, seconds, microseconds,                                        milliseconds, minutes,                                        hours, weeks, months, years)

   到目前为止,我么已经完成了该类的最重要的两个操作,构造函数的重载以及 dayDelta系列函数的实现。那么接下来,我们就需要实现另外一个比较常见的功能,就是不同时间字符串形式的相互转换——formatChange,该函数的功能就是将一个时间的字符串转化为另外一种格式,比如将 '2014-01-01' 转化成 '20140101'。函数的实现很简单,调用响应的构造函数并做目标格式化的输出操作。该函数具体的实现代码如下: 

    @classmethod    def formatChange(self, string, inFormat = "%Y-%m-%d", outFormat = "%Y%m%d"):        try:            return DateTimeUtil(string, inFormat).format(outFormat)        except Exception:            raise

   时间类最常见的重点功能我们已经实现,但是还有一个我认为比较重要的函数会让一些对时间迭代操作的人非常喜欢,那就是range函数,函数的功能和python的全局函数range很像,返回两个日期直接的列表,列表中的每个值都在这两天之间(开闭区间),这样一来,我们需要某几天连续的时间会变得更加方便。下面是 range 函数的具体实现: 

    def range(self, endDate, sep = 1):        sd, dates, e = self, [], endDate - 1        while sd <= e:            dates.append(sd)            sd = sd + 1        return dates

   上述三个时间类功能函数从三个方面完成了相关的任务,当然为了更好的服务我们的开发人员,让我们的参观者看到的不仅仅是骨架,我们还需要提供常见的基本函数,yesterday tomorrow nowtoday format firstDay lastDay +-等操作符的重载以及时间上各种常量的返回(年月日时分秒……)。下面是部分函数的实现代码:

    def __add__(self, days):        return DateTimeUtil.dayDelta_s(self, days)    # sub, -    def __sub__(self, days):        return DateTimeUtil.dayDelta_s(self, -days)
    def __lt__(self, cmpDate):        try:            return self.__value < cmpDate.datetime()        except:            raise

   至此,python的设计以及实现已经完成,最后,还有一点需要强调的就是,python中对类'='操作,默认的是地址的拷贝,也就是说如果执行 objA = objB 的话,那么 objA objB的内容完全一样,而且任何一个值的修改都会影响另外一个值。所以,为了让用户能够使用值拷贝的方式进行赋值,我们需要提供一个 copy assign函数实现值的拷贝(python不支持'='重载,因为'='是表达式,不是操作符)。具体的实现可以参看整体的代码。

   下面是整个函数的具体代码实现: 

class DateTimeUtil:    # the init function    # the flag value is in 'STRING', 'DATETIME', 'VALUES', 'TIMESTRUCT'    def __init__(self, one, two = '', three = '',                 four = '', five = '', six = '', seven = ''):        self.__value = self.__factory(one)(one, two, three, four, five, six, seven)        # self.__maxDay = calendar.monthrange(self.__value.year, self.__value.month)[1]        self.__def__format = "%Y%m%d"        # print(type(self.value))        # self.value = datetime.datetime(2014, 12, 1)        # return self    # wrapper function    def __factory(self, flag):        if isinstance(flag, str):            return lambda dStr, dFormat, *more: self.__dealWithString(dStr,                                                                      ThreeOperator(dFormat, dFormat, "%Y%m%d"),)        elif isinstance(flag, time.struct_time):            return lambda timeStruct, *more: self.__dealWithTimeStruct(timeStruct)        elif isinstance(flag, (int, float)):            return lambda year, month, day, hour, minute, second, millisecond: \                self.__dealWithValues(year, ThreeOperator(isinstance(month, int), month, 1),                                      ThreeOperator(isinstance(day, int), day, 1),                                      ThreeOperator(isinstance(hour, int), hour, 0),                                      ThreeOperator(isinstance(minute, int), minute, 0),                                      ThreeOperator(isinstance(second, int), second, 0),                                      ThreeOperator(isinstance(millisecond, int), millisecond, 0),)        elif isinstance(flag, datetime.datetime):            return lambda dtime, *more: self.__dealWithDateTime(dtime)    # deal with the string format    def __dealWithString(self, dStr, dFormat):        timeS = time.strptime(dStr, dFormat)        return self.__dealWithTimeStruct(timeS)    # time_struct    def __dealWithTimeStruct(self, timeStruct):        if not isinstance(timeStruct, time.struct_time):            raise AttributeError        return self.__dealWithValues(timeStruct.tm_year, timeStruct.tm_mon,                                     timeStruct.tm_mday, timeStruct.tm_hour,                                     timeStruct.tm_min, timeStruct.tm_sec)    # deal with the datetime object    def __dealWithDateTime(self, dtime):        return dtime    # deal with the values    def __dealWithValues(self, year, month = 1, day = 1,                         hour = 0, minute = 0, second = 0,                         milliseconds = 0):        try:            maxDay = calendar.monthrange(year, month)[1]            if day < 0:                day = maxDay + 1 + day            return datetime.datetime(year, month, day, hour,                                     minute, second, milliseconds)        except Exception:            raise    def dayDelta(self, days = 0, seconds = 0, microseconds = 0,                 milliseconds = 0, minutes = 0, hours = 0,                 weeks = 0, months = 0, years = 0):        # year, month, day        newYear, newMonth, newDay = self.__value.year, self.__value.month, self.__value.day        if years != 0 or months != 0:            totalMonths = (self.__value.year + years) * 12 + \                          (self.__value.month + months) - 1            newYear = int(totalMonths / 12)            newMonth = totalMonths % 12 + 1            newMaxDay = calendar.monthrange(newYear, newMonth)[1]            newDay = ThreeOperator(self.__value.day > newMaxDay, newMaxDay, self.__value.day)        self.__value = datetime.datetime(newYear, newMonth, newDay, self.__value.hour,                                         self.__value.minute, self.__value.second,                                         self.__value.microsecond)        newDate = self.__value + datetime.timedelta(days, seconds, microseconds,                                                    milliseconds, minutes, hours, weeks)        self.__init__(newDate)        return self    @classmethod    def dayDelta_s(self, orignalDate, days = 0, seconds = 0, microseconds = 0,                   milliseconds = 0, minutes = 0, hours = 0,                   weeks = 0, months = 0, years = 0):        orignalDateCopy = orignalDate.copy()        return orignalDateCopy.dayDelta(days, seconds, microseconds,                                        milliseconds, minutes,                                        hours, weeks, months, years)    # some getter methor, maxday, year, month,    # day, hour, minute, second, millisecond    def maxDay(self):        return calendar.monthrange(self.__value.year, self.__value.month)[1]    # if the current date is first Date    def firstDay(self, flag = 'MONTH', days = 1):        results = self.__value.day == 1        if flag == 'YEAR':            results = results and (self.__value.month == 1)        return results    # if the current date is last Date    def lastDay(self, flag = 'MONTH', days = 1):        results = self.__value.day == self.maxDay()        if flag == 'YEAR':            results = results and (self.__value.month == 12)        return results    # return datetime class    def datetime(self):        return self.__value    # tomorrow    def tomorrow(self):        self.dayDelta(1)        return self    def yesterday(self):        self.dayDelta(-1)        return self    # the year of current date    def year(self):        return self.__value.year    # the month of currrent date    def month(self):        return self.__value.month    # the day of current date    def day(self):        return self.__value.day    # the hour of current date    def hour(self):        return self.__value.hour    # the minute of current date    def minute(self):        return self.__value.minute    # the second of current date    def second(self):        return self.__value.second    # the weekday of current day    def weekday(self):        return self.__value.weekday()    # the microsecond of current day    def microsecond(self):        return self.__value.microsecond    # format the current date and time to string    def format(self, outFormat = "%Y-%m-%d %H:%M:%S", isoFormat = False):        try:            if isoFormat:                return self.__value.isoformat()            return self.__value.strftime(outFormat)        except Exception:            raise    # return the today    @classmethod    def today(self):        return DateTimeUtil(datetime.datetime.today())    # return the now    @classmethod    def now(self):        return DateTimeUtil(datetime.datetime.now())    # return the utc now    @classmethod    def utcnow(self):        return DateTimeUtil(datetime.datetime.utcnow())    # time format    @classmethod    def formatChange(self, string, inFormat = "%Y-%m-%d", outFormat = "%Y%m%d"):        try:            return DateTimeUtil(string, inFormat).format(outFormat)        except Exception:            raise    # range    def range(self, endDate, sep = 1):        sd, dates, e = self, [], endDate - 1        while sd <= e:            dates.append(sd)            sd = sd + 1        return dates    # some operator override    # add, +    def __add__(self, days):        return DateTimeUtil.dayDelta_s(self, days)    # sub, -    def __sub__(self, days):        return DateTimeUtil.dayDelta_s(self, -days)    # print    def __str__(self):        return self.format(isoFormat = True)    # equal X == Y    def __eq__(self, cmpDate):        try:            return self.__value == cmpDate.datetime()        except:            raise    # not equal X != Y    def __ne__(self, cmpDate):        try:            return self.__value != cmpDate.datetime()        except:            raise    # less then X < Y    def __lt__(self, cmpDate):        try:            return self.__value < cmpDate.datetime()        except:            raise    # less equal X <= Y    def __le__(self, cmpDate):        try:            return self.__value <= cmpDate.datetime()        except:            raise    # grater then X >= Y    def __gt__(self, cmpDate):        try:            return self.__value > cmpDate.datetime()        except:            raise    # greater equal X >= Y    def __ge__(self, cmpDate):        try:            return self.__value >= cmpDate.datetime()        except:            raise    # the assign for =    def assign(self):        return DateTimeUtil(self.datetime())    # the copy    def copy(self):        return DateTimeUtil(self.datetime())

   上述时间类的设计以及最终的实现,主要的目的有一下几个:

    1、实现python中构造函数的重载操作。

    2、对一个公共类的设计,我们一定要考虑到是否修改类本身的值,注意python'='的赋值方式。

    3、公共类的设计,需要考虑不同的人的使用需求和习惯,对相同功能的函数,提供不同的函数名。

    4、对于常见的操作符,我们需要提供重载的操作,让用户使用更加的习惯。

   以上类的设计过程主要是为了抛砖引玉,希望大家能够设计出更加方便易用的公共类。

0 0
原创粉丝点击