1、使用模板模式和工厂模式的混合设计开发各种邮件客户端发送邮件。
2、模板模式的目的:能保证快速开发各种邮箱客户端,子类只需要重写模板类邮箱的抽象方法即可。之后再开发任何邮箱就只要加一个类,写3行代码就可以。
工厂模式的目的:能隐藏创建具体对象的细节,只需从工厂类的方法中就能得到各种邮箱客户端。例如被别的模块import时候,只需要import这个工厂类就可以了,不需要去import几十种具体的邮箱客户端。
# coding=utf8"""使用模板模式、工厂模式的混合设计,开发各种邮件客户端"""import sysreload(sys)sys.setdefaultencoding('utf8')import reimport unittestfrom abc import ABCMeta, abstractmethodimport smtplibfrom email.mime.text import MIMETextclass MailClient(object): """一个通用的邮件客户端,作为模板模式的模板类。 该类为抽象类,不可以进行实例化。 _set_smtp方法为抽象方法,子类必须重写这个方法. """ __metaclass__ = ABCMeta def __init__(self, msg_from, passwd): """ :param msg_from: 发件人邮箱地址 :param passwd: 发件人邮箱密码,qq邮箱使用授权码是16个字母,而不是自己的邮箱密码。 """ self._mail_name = None self._msg_from = msg_from self._passwd = passwd self._smtp = None self._set_smtp() self.__login() @abstractmethod def _set_smtp(self): pass def __login(self): try: self._smtp.login(self._msg_from, self._passwd) except smtplib.SMTPAuthenticationError as e: raise smtplib.SMTPAuthenticationError(535, '{}邮箱登录失败\n'.format(self._mail_name) + e.message) def send_mail(self, msg_to, subject, content): """ 发送邮件 :param msg_to: 所有收件人的邮箱地址列表,类型为字符串列表,单个接收人也可以用字符串 :param subject :邮件主题 :param content:邮件内容 :return :邮件是否发送成功 :type msg_to:list :type subject:str :type content:str """ msg = MIMEText(content, _charset='utf8') msg['Subject'] = subject msg['From'] = self._msg_from msg['To'] = msg_to self._smtp.sendmail(self._msg_from, msg_to, msg.as_string()) print '邮件发送成功,发送的邮件主题是: {} 。请去邮箱检查邮件'.format(subject) return True def __str__(self): return '登录帐号为: {0} 的{1}邮箱客户端'.format(self._msg_from, self._mail_name) def quit(self): try: # 如果没登陆成功就关闭连接,会出错,try一下 self._smtp.quit() except: pass def __del__(self): self.quit()class QQMailClient(MailClient): def _set_smtp(self): self._mail_name = 'qq' self._smtp = smtplib.SMTP_SSL("smtp.qq.com", 465)class Wangyi163MailClient(MailClient): def _set_smtp(self): self._mail_name = '163' self._smtp = smtplib.SMTP_SSL("smtp.163.com", 465)class MailNameException(Exception): """不支持的邮箱异常类""" def __init__(self, msg_from): err = '你设置的邮箱账号是 {} ,不支持此邮箱'.format(msg_from) super(MailNameException, self).__init__(err)class MailClientFactory(object): @staticmethod def get_mail_client(msg_from, passwd): """ :param msg_from:发件人邮箱地址 :param passwd:邮箱密码 :return: 具体的邮箱对象 :type msg_from:str :type passwd:str :rtype :MailClient """ if re.match('[0-9a-zA-Z_]+@qq\.com', msg_from): return QQMailClient(msg_from, passwd) elif re.match('[0-9a-zA-Z_]+@163\.com', msg_from): return Wangyi163MailClient(msg_from, passwd) else: raise MailNameException(msg_from)class MailTest(unittest.TestCase): def test_163(self): """测试163邮箱发送两个邮件""" wangyi163_mail_client = MailClientFactory.get_mail_client('m13148804506@163.com', '1234567') print wangyi163_mail_client wangyi163_mail_client.send_mail('909686716@qq.com', '测试网易163邮箱发送主题1', '测试网易163邮箱发送内容1') result = wangyi163_mail_client.send_mail('909686716@qq.com', '测试网易163邮箱发送主题2', '测试网易163邮箱发送内容2') self.assertEqual(result, True, msg='163邮件发送失败') def test_qq(self): """测试两个qq号发送邮件""" qq_123456_client = MailClientFactory.get_mail_client('123456@qq.com', 'ssoodruxniyfxxxx') print qq_123456_client qq_789000_client = MailClientFactory.get_mail_client('789000@qq.com', 'ssoodruxniyfxxxx') print qq_789000_client result = qq_123456_client.send_mail('m13148804506@163.com', '测试qq邮箱发送主题3', '测试qq邮箱发送内容3') self.assertEqual(result, True, msg='qq邮件发送失败') result = qq_789000_client.send_mail('m13148804506@163.com', '测试qq邮箱发送主题4', '测试qq邮箱发送内容4') self.assertEqual(result, True, msg='qq邮件发送失败') def test_126(self): """测试一个不支持的邮箱""" self.assertRaises(MailNameException, MailClientFactory.get_mail_client, 'm13148804506@126.com', '123456')if __name__ == "__main__": unittest.main()
3、由此可见,在一个项目中设计模式不是固定的只能用一种,而是可以多种模式混合设计。
1、此项目也可以使用 策略模式来实现,策略类中设置邮箱服务器,Context类中登录和发送邮件,和此文比只有很小的区别。策略模式实现可以参考
2、由于这个邮箱项目比较简单,这也可以不使用设计模式,不需要增加多个类,只需要一个类就可以实现了。工厂模式的缺点是每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。过度设计这并不是什么好事。
3、也可以用面向过程来实现,缺点是在进行多实例操作时候,小红 小明 小黄登录qq邮箱,需要在类之外设置变量,如果类的属性非常多,比如20个,小红 、小黄 、小明需要在类外设置60个变量来保存这些属性,oop只要3个对象,每个对象有20个属性。oop在状态维护、保存、序列化反序列化上、被别的模块调用更简单、命名空间更清洁、继承这些方面有很大优势。
看到有的人是一个函数就实现发邮件,虽然简单,把设置服务、登录、发送邮件、关闭连接放一起了,那样做每发一个邮件都执行连接服务器进行登录发邮件关闭连接这个过程,有一些额外的开销。