- 浏览: 69631 次
- 性别:
- 来自: 绍兴
最新评论
在我刚接触程序的时候我觉得设计模式没啥用,只要能解决问题完成任务找得到工作拿得到工资就行了,后来编码一段时间我又感觉设计模式主要对那些搭建框架的大牛有用,再后来我又感觉其实大部分需要用到设计模式的编码spring已经帮我们完成了,我们在ssh框架中完成自己的业务就行了,设计模式对我还是没那么重要,再后来我发现其实我一直在使用设计模式只是我自己不知道而已,所以现在我认为设计模式不是一种标准,而是一种心态,当你成熟了懂事了,你自然会去设想出一个你觉得可维护性可扩展性高且结构清晰的代码。
一个项目团队的架构是一样的,但是各位程序员编码的思路是截然不同的,好的代码设计不仅大大增加开发效率,也可以让代码的结构层次看起来非常清晰,便于后期的维护,虽然你的编码思路和我不一致,但是如果你的代码设计的够好,那我可以跟着你的思路走从而帮你做后期维护工作。
面试的时候经常会被问及是否看过一些开源框架的源码,其实面试官的目的不是在于你看了多少框架,他只是想用一个使用了很多设计模式的媒介来考你对设计模式的理解,所以一般你说看过某某框架以后,他都会进一步问你其中的编码细节,如果你能对框架中用到的设计模式描绘一二那很有可能帮你拿到好的面试分,确实对于我们程序员来说看多少源码不是目的,学习它的编码思路才是关键。
编码的思路是千变万化传授不来的,前辈们已经归结出了27种设计模式来帮助我们总结了,关于设计模式的文章网上不计其数,但是如果你没有相关编码经历你读再多看再细都是没啥大效果的,就跟初学者不适合看源码一样。
今天我想用一个简单的例子来说明一下设计模式是无处不在的,同时也展现下充分的编码设计能让代码的结构变的更好,更便于维护。
这个例子是用spring封装的工具类发送邮件,你是否会觉得这种单一的功能网上随便找找例子搬运过来一个工具类就搞定了,如果你是这种心态那再好不过了,你跟着我的思路走看看有没有可以学习的地方。
首先需要先了解主体属性,一个邮件一般包含的属性为标题,内容,附件,发送者和接收者,如图
除了那些属性外,邮件还包含一个被发送的动作,这个发送的动作有很多方式可以实现,我这里用到了命令模式加访问者,且看我的实体类代码,不错一个简单的pojo类其实就已经包含设计模式了
package com.sdh.framework.owl.common.mail; import java.io.File; import java.io.Serializable; import java.util.Map; public class MailContent implements Serializable { private static final long serialVersionUID = 6221804960111125452L; /** * 邮件标题 */ private String subject; /** * 发送方 */ private String fromAddress; /** * 接收方 */ private String [] toAddress; /** * 邮件内容 */ private String content; /** * 邮件附件 */ private Map<String, File> fileMap; /** * 邮件类型 */ private MailSenderTypeEnum mailSenderType; //发送动作 public void doSend(){ if(mailSenderType == null){ mailSenderType = MailSenderTypeEnum.SIMPLE; } mailSenderType.getMailSender().doSend(this); } public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } public String getFromAddress() { return fromAddress; } public void setFromAddress(String fromAddress) { this.fromAddress = fromAddress; } public String[] getToAddress() { return toAddress; } public void setToAddress(String[] toAddress) { this.toAddress = toAddress; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public Map<String, File> getFileMap() { return fileMap; } public void setFileMap(Map<String, File> fileMap) { this.fileMap = fileMap; } public void setMailSenderType(MailSenderTypeEnum mailSenderType) { this.mailSenderType = mailSenderType; } }
当业务类持有MailContent实例的时候,对它进行doSend方法调用就形成了命令模式,业务类并不需要关心MailContent是怎么把邮件发出去的,它只要发出一个发送的命令就行,在doSend方法内部引入一个包含发送逻辑的枚举,同时在MailContent调用发送逻辑的时候把自身传给了它,这样发送逻辑就可以访问到MailContent的数据了,这就形成了访问者模式,访问者不关心你的数据是怎么组装的,他只需要根据访问到的数据对MailContent进行发送逻辑就可以了。
再来说说邮件类除了刚才说的属性外又多了一个邮件类型的枚举类,这个枚举的值包括:简单、html、包含图片、包含附件四种类型,当然也不排除后期有其他的扩展,所以我们要做好对这块的扩展接口。看看这个枚举的代码
package com.sdh.framework.owl.common.mail; public enum MailSenderTypeEnum { SIMPLE, HTML, IMAGE, ATTACHMENT; public AbstractMailSender getMailSender() { switch (this) { case SIMPLE: return new SimpleMessageMailSender(); case HTML: return new HtmlMailSender(); case IMAGE: return new ImageMailSender(); case ATTACHMENT: return new AttachmentMailSender(); default: break; } return null; } }
建立一个多态返回类型(AbstractMailSender)的getMailSender方法,根据类型值来创建不同的发送逻辑类,显而易见用到了工厂模式,看到这里如果你是一个老程序员你肯定对剩余部分的代码结构有初步估盼了,在现实开发中正式这种估盼的欲望让我们继续有动力去view别人的代码从而帮他维护,举个例子如果线上出现发送图片邮件失败的问题那直接找到ImageMailSender类的相关代码就可以去解决了,而不是在一个工具类中看别人写的if逻辑。估盼的逻辑应该就是有个抽象类AbstractMailSender 包含参数为邮件体的doSend抽象方法,SimpleMessageMailSender HtmlMailSender ImageMailSender AttachmentMailSender四个类继承了AttachmentMailSender并实现了各自的doSend方法,看下代码结构图
跟估盼的还是有点差距的,AbstractMailSender的doSend并不是抽象方法,而是多了一个sendMail的抽象方法,除了简单文本的实现类是继承AbstractMailSender的,其他都是间接继承的,这什么原因呢?对,充分的代码复用,代码复用在编码设计中是最基本也最实用的,极大减轻工作量以及减少bug的滋生,以前一个华为的老工程师对我说的一句话我觉得还蛮有道理,“程序员要有充分的api思想”,一个优秀的程序员在完成自己任务的同时,还能把其中的公共部分抽出来供后来者使用,如果一个团队中人人都是这种思想那别提有多舒服。我们开始解析代码吧,首先看看AbstractMailSender类
package com.sdh.framework.owl.common.mail; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Properties; import org.springframework.mail.javamail.JavaMailSenderImpl; import com.sdh.framework.owl.common.util.ValidTool; /** * 邮件发送抽象类 * @author hadoop * */ public abstract class AbstractMailSender { /** * spring 封装java邮件发送 */ protected JavaMailSenderImpl senderImpl = null; //加载邮箱配置信息 private static Properties props = new Properties(); static{ try { props.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("mail.properties")); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * 发送邮件业务逻辑方法 需要子类自己实现 * @param content 发送内容 */ protected abstract void sendMail(MailContent content); /** * 发送邮件调用方法 * @param content 发送的内容 */ public void doSend(MailContent content) { //对邮件内容做非空验证 ValidTool.validIsNull(content,"邮件内容"); ValidTool.validIsNull(content.getContent(), "邮件内容"); ValidTool.validIsNull(content.getSubject(), "邮件标题"); ValidTool.validIsNull(content.getToAddress(), "接收列表"); //基本信息配置 if(this.senderImpl == null){ senderImpl = new JavaMailSenderImpl(); senderImpl.setHost(props.getProperty("hosts")); senderImpl.setUsername(props.getProperty("user_name")); senderImpl.setPassword(props.getProperty("password")); content.setFromAddress(props.getProperty("email_address")); Properties prop = new Properties(); prop.put("mail.smtp.auth", props.getProperty("mail.smtp.auth")); prop.put("mail.smtp.timeout", props.getProperty("mail_smtp_timeout")); senderImpl.setJavaMailProperties(prop); } //调用子类业务逻辑发送邮件 sendMail(content); } }
AbstractMailSender只对发送的基础做了定义,不做具体的发送逻辑,就像麻将室的老板给我们提供了一张自动麻将桌,具体你打什么麻将,打多大他是不管的,doSend方法中先是对客户端发送的邮件内容做简单的校验,就像老板对于来打麻将的客人要有个信任度,你会不会是打了麻将不给钱的人?你会不会是派出所的卧底?然后用一个单例模式做参数实例化,对,就算你打多少圈麻将,麻将桌子一旦定下来是不会变的。最后调用供子类实现的抽象方法,这其实就是一个模板模式,也被叫做钩子模式,一个我比较喜欢的模式,在之前一篇关于struts基类的博客中也有提到过。然后我们继续看简单文本发送的逻辑类
package com.sdh.framework.owl.common.mail; import org.springframework.mail.SimpleMailMessage; /** * 简单文本发送 * @author hadoop * */ public class SimpleMessageMailSender extends AbstractMailSender { @Override public void sendMail(MailContent content) { SimpleMailMessage mailMessage = new SimpleMailMessage(); mailMessage.setTo(content.getToAddress()); mailMessage.setFrom(content.getFromAddress()); mailMessage.setSubject(content.getSubject()); mailMessage.setText(content.getContent()); senderImpl.send(mailMessage); } }
spring封装了SimpleMailMessage来做简单文本发送,逻辑比较简单提供相应的参数后传递给JavaMailSenderImpl的send方法就可以发送,这个就好像我们定义好了规则开始在麻将桌上打起了麻将,只是我们这次的规则有点简单,不吃不碰无花无财神打法比较简单,这里还有一点需要注意其实这里也同样用到命令模式,我们封装好一个mailMessage以后传递给senderImpl然后命令他发送,具体spring怎么处理怎么操作javamail我们是不管的,不过可想而知支撑JavaMailSenderImpl.doSend的类不会只有他自己,应该也是会有一个类的结构图。再来看看富文本逻辑类抽象类
package com.sdh.framework.owl.common.mail; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import org.springframework.mail.javamail.MimeMessageHelper; /** * 富文本邮件发送抽象类 * @author hadoop * */ public abstract class AbstractMimeMessageMailSender extends AbstractMailSender { protected MimeMessage mailMessage = null; @Override public void sendMail(MailContent content){ //获取spring富文本消息对象 mailMessage = this.senderImpl.createMimeMessage(); //用富文本辅助类对消息对象进行装饰 MimeMessageHelper helper = getMimeMessageHelper(mailMessage); //调用子类设置 消息对象的业务逻辑 setMimeMessage(content,helper); //发送消息体 senderImpl.send(mailMessage); } /** * 获取富文本辅助类 并对消息体进行装饰 * @param mailMessage 消息体 * @param content 发送内容 * @return 富文本辅助类 */ protected MimeMessageHelper getMimeMessageHelper(MimeMessage mailMessage) { MimeMessageHelper messageHelper = null; try { messageHelper = new MimeMessageHelper(mailMessage, true, "utf-8"); } catch (MessagingException e) { // TODO Auto-generated catch block e.printStackTrace(); } return messageHelper; } /** * 子类设置消息体抽象方法 * @param content */ protected abstract void setMimeMessage(MailContent content,MimeMessageHelper helper); }
富文本的逻辑相对来讲有意思的多,首先富文本的消息体是有JavaMailSenderImpl来创建的,这个其实可以看做是建造者模式,构建的逻辑由JavaMailSenderImpl来决定,我们只需关心建造出来的东西能被我们正常使用就可以了,就如麻将机的例子,我们不管麻将子是什么材料 多少大小的,对于我们来说只要能摸能打就行了,至于麻将子的底层是什么以及麻将机怎么对子进行洗牌是不需要我们去了解,这就是建造者模式的特性,我需要什么,你给我造什么,只要我能用其他我不管,当我用完以后你还要给我提供售后服务。同时spring还给富文本邮件发送封装了MimeMessageHelper类,该类可以视作是一个装饰器模式,把我们的邮件体给他以后他会对我们的邮件体做格式装饰,比如需要符合各邮件服务器的标准等,装饰完以后还是会还给我们由我们自己来操作是否发送,就像有的麻将机会把原始的手动掷筛子装饰成震动筛子一样,他只对掷筛子的功能做了修饰,不会改变我们打麻将的流程。再看setMimeMessage方法,这个是更容易理解的模板模式,他是被嵌在代码逻辑里面,子类只需做好对装饰器类的参数配置就行,真正的发送逻辑由父类统一处理。接下去就看下AbstractMimeMessageMailSender的子类HtmlMailSender
package com.sdh.framework.owl.common.mail; import javax.mail.MessagingException; import org.springframework.mail.javamail.MimeMessageHelper; /** * html内容邮件发送 * @author hadoop * */ public class HtmlMailSender extends AbstractMimeMessageMailSender { @Override public void setMimeMessage(MailContent content,MimeMessageHelper messageHelper) { try { //设置邮件内容信息 messageHelper.setTo(content.getToAddress()); messageHelper.setFrom(content.getFromAddress()); messageHelper.setSubject(content.getSubject()); // true 表示启动HTML格式的邮件 messageHelper.setText(content.getContent(), true); } catch (MessagingException e) { e.printStackTrace(); } } }
HtmlMailSender只做了自己的本分工作,把相关需要装饰的内容装备到装饰类,并不需要做发送操作,再继续看图片邮件和附件邮件的代码,因为这两种邮件都是基于html邮件的,所以他们可以继承HtmlMailSender来减少重复的代码
package com.sdh.framework.owl.common.mail; import javax.mail.MessagingException; import org.springframework.core.io.FileSystemResource; import org.springframework.mail.javamail.MimeMessageHelper; /** * 发送包含图片的邮件 * @author hadoop * */ public class ImageMailSender extends HtmlMailSender { @Override public void setMimeMessage(MailContent content,MimeMessageHelper messageHelper) { try { //调用父类邮件内容设置 可以在设置前对content做逻辑修改 super.setMimeMessage(content, messageHelper); String t = content.getContent(); //对邮件内容的文件做解析 if (content.getFileMap() != null) { for (String key : content.getFileMap().keySet()) { t=t.replace("{" + key + "}", "cid:" + key); } messageHelper.setText(t,true); for (String key : content.getFileMap().keySet()) { FileSystemResource file = new FileSystemResource(content.getFileMap().get(key)); messageHelper.addInline(key, file); } } } catch (MessagingException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
package com.sdh.framework.owl.common.mail; import javax.mail.MessagingException; import org.springframework.core.io.FileSystemResource; import org.springframework.mail.javamail.MimeMessageHelper; public class AttachmentMailSender extends HtmlMailSender { @Override public void setMimeMessage(MailContent content,MimeMessageHelper messageHelper) { try { super.setMimeMessage(content, messageHelper); if(content.getFileMap() != null){ for (String key : content.getFileMap().keySet()) { FileSystemResource file = new FileSystemResource(content.getFileMap().get(key)); messageHelper.addAttachment(key, file); } } } catch (MessagingException e) { e.printStackTrace(); } } }
super.setMimeMessage(content, messageHelper); 就可以把父类的逻辑在自己的逻辑里先调用一遍,这种做法的好处是以后不用再关心html邮件的逻辑有什么改动,比如需要在html邮件的内容前统一加个公司名称什么的等。同时他们也用到了简单迭代模式来遍历附件对其做封装处理。最后来说明下虽然这种设计比普通的工具类多了好多类,但是这种方式所有类都各司其职结构清楚,并且减少逻辑复杂度,不管哪种类型的邮件发送只要一旦被实例化之后他就是专门为一种邮件类型而生的,所以减少逻辑判断,就跟java的泛型差不多。到这里差不多写完了,虽然这个博客写的有点长但又觉得还有好多没讲,不过确实设计模式这东西其实光靠讲还是很难讲清楚的,在实践中成长才是硬道理,最后看下调用,也就是一开始说的命令模式。想探讨的朋友可以加我qq。
MailContent content = new MailContent(); content.setContent("<html><head></head><body><h1>hello!!spring image html mail</h1>" + "<img src=\"{aaa}\"/></body></html>"); content.setSubject("封装测试标题"); content.setToAddress(new String[]{"593943439@qq.com"}); Map<String, File> fileMap = new HashMap<>(); fileMap.put("aaa", new File("C:/Users/tb_donghui.sun/Pictures/222.jpg")); content.setFileMap(fileMap); content.setMailSenderType(MailSenderTypeEnum.IMAGE); content.doSend();
发表评论
-
Web开发学习13聊聊java反射
2016-07-12 18:41 765很喜欢一本叫《走出软件作坊》的书,其中有一句话让我较为深刻,“ ... -
Web开发学习11 全局缓存控制
2015-10-12 14:27 747缓存用的好可以减少数据库的压力,在大并发的情况下极大的提升服务 ... -
Web开发学习10 hadoop实战
2015-10-09 14:02 763前一篇说到项目记录了很多埋点日志,当有一天项目需求需要对这些日 ... -
Web开发学习(9)全局埋点
2015-07-27 11:10 2025埋点的作用是把客户端每次访问服务端的操作记录下来,包括请求连接 ... -
Web开发学习8Struts2基类封装
2015-07-24 12:03 710一个好的基类可以帮助开发者减少很多开发工作,像我这种爱偷懒的人 ... -
Web开发学习7按钮级别权限控制
2015-07-23 16:21 13355这个功能的思路是自己原创的,没看过其他例子,其实是在做之前网上 ... -
Web开发学习6添加liqiubase
2015-07-22 14:29 1467开发过程中经常碰到数据库更改的情况,在日常环境下如果每位开发人 ... -
浅列JavaEnum
2014-01-05 16:42 791enum Province{ ZHEJIANG,SHANGHA ... -
maven常用命令
2013-07-08 10:20 7921、创建普通Java项目: ... -
canvas绘制科赫雪花
2012-12-20 17:24 2162<html> <head> ... -
html5 audio与video方法属性事件概括
2012-12-20 14:46 1124play() 继续播放 pause() ... -
spring 整合mina
2012-12-20 11:48 1123首先定义自定义过滤器 <bean id=" ... -
Web开发学习(5)添加springsecurity应用
2011-12-14 15:00 965我学习公司这个架构多半就是为了把springsecurity搞 ... -
Web开发学习(4)添加spring应用
2011-12-14 14:45 798印象中似乎没做过不用spring的项目,因为它在web开发中的 ... -
Web开发学习(3)配置struts-tiles插件(附带json插件)
2011-12-05 15:22 2573struts的tiles插件是非常实用的,从某些方面可以很好的 ... -
使用线程删除导出临时文件
2011-12-01 16:33 2277项目支持大数据量导出e ... -
jQuery实现表格行点击选中复选框
2011-11-23 12:36 6009这个需求是在项目完成后客户提出的要求,看似简单但是还需要一点小 ... -
Web开发学习(2)配置convention插件
2011-11-04 10:28 1396注解的方式某些程度上能减少xml的配置量,个人感觉使程序更加清 ... -
Web开发学习(1)使用eclipse搭建maven项目
2011-10-20 10:04 5749首先用eclipse创建工程 ne ... -
数据库连接泛型基类创建
2011-06-30 11:49 1003记录 以便以后参考---- 基类代码 public cl ...
相关推荐
SSH框架,全称为Struts+Spring+Hibernate,是Java企业级开发中常用的一种技术栈,主要用于构建基于MVC(Model-View-Controller)设计模式的Web应用。在本篇文章中,我们将深入探讨这三个组件的核心概念和进阶用法,...
MVC(Model-View-Controller)设计模式是一种广泛应用于软件工程,尤其是Web开发中的架构模式。它将应用程序分为三个主要组件:模型(Model)、视图(View)和控制器(Controller)。这种分离使得软件的各个部分可以...
在项目开发中,需要遵守一定的开发实施要点,比如利用微信小程序提供的扫码登录功能和WebAPI进行开发,这样可以快速创建项目,减少开发成本。同时,通过绑定OpenID机制,可以实现用户的快速登录,提升用户体验。...
"原生APP开发公司浅谈原生开发、H5开发与混合开发的区别.pdf" 在移动应用开发领域中,开发模式的选择对应用的性能、用户体验和开发效率都有着至关重要的影响。原生开发、H5开发和混合开发是当前市场上三种主流的...
3) 结构设计:采用MVC(Model-View-Controller)或其他设计模式组织代码。 4) 编写测试脚本:使用页面对象模式,实现自动化测试用例。 5) 数据管理:设计数据驱动策略,存储和加载测试数据。 6) 错误处理:设置合理...
MVC(Model-View-Controller),即模型-视图-控制器模式,是一种广泛应用于软件开发领域的设计模式,特别是在桌面应用和Web应用程序中。这种模式的核心思想是将应用程序分为三个相互独立的主要组成部分:模型(Model...
浅谈 PHP 技术 PHP(Hypertext Preprocessor)是一种服务器端的 HTML 嵌入式脚本语言,具有易于学习和使用的特点,语法混合了 C 语言、Java 语言和 Perl 语言的特点,是一种被广泛应用的开源式的多用途脚本语言,...
设计模式是为了解决软件设计中的常见问题而提出的解决方案,但滥用或不恰当应用设计模式可能会增加代码复杂性,反而降低效率。正确理解和适时应用设计模式,如单例模式、工厂模式和观察者模式,有助于提高代码质量。...
MVC设计模式是Web开发中的一种重要模式,它帮助开发者组织代码,使其结构更加清晰,有利于团队协作和项目的长期发展。通过理解MVC的原理和实践,开发者能够更好地构建可扩展、可维护的Web应用程序。
【文章概述】 本文探讨了基于1+X证书制度下的高职《Web前端设计》课程考核改革的必要性和具体策略。...这一改革有助于更准确地评估学生学习成果,提高考核质量,为培养应用型、技能型的Web前端设计人才奠定坚实基础。
本文将深入探讨三种常见的设计模式:Model-View-Controller(MVC)、Model-View-Presenter(MVP)和Model-View-ViewModel(MVVM)。这些模式在构建用户界面时起着至关重要的作用,尤其是在Web和移动应用开发中。 ...
使用Struts的目的是为了帮助我们减少在运用MVC设计模型来开发Web应用的时间。如果我们想混合使用Servlets和JSP的优点来建立可扩展的应用,struts是一个不错的选择。 1 Struts框架应用模式 1)建立在MVC这种公认...
### 浅谈JavaScript库——jQuery与ExtJs的对比研究 #### 一、引言 随着Web 2.0和RIA(Rich Internet Applications)技术的普及,JavaScript作为一种前端开发的重要工具,逐渐占据了重要的地位。它不再仅仅是用于...
### ASP服务器脚本语言浅谈 #### 一、ASP的起源与定义 **ASP**(Active Server Pages)是由微软开发的一种服务器端脚本环境,用于创建动态网页或Web应用程序。它是一种简单而便捷的编程工具,使得开发人员能够轻松地...
- **软件工程原则**:学习软件工程的最佳实践,如设计模式、重构技术等。 - **软技能培养**:培养良好的沟通能力、团队合作精神等软技能。 #### 结语 学习Java是一项长期的过程,需要持之以恒的努力和不断实践的...
另外,随着前后端分离的趋势,现代Web应用更多地采用AJAX和RESTful API,但JSP仍然是学习Web开发基础和理解服务器端动态渲染的一个重要工具。 总的来说,JSP作为Java Web开发的一种方式,虽然在现代Web开发中可能...
### 浅谈框架与MVC #### 架构的重要性及概念 在软件开发领域,架构设计扮演着至关重要的角色。良好的架构设计不仅能够确保软件系统的稳定性、可维护性和可扩展性,还能提升团队协作效率。正如文章所述,“好的产品...
此外,由于大多数Web开发工具和开源技术的可用性,降低了开发和维护成本。 9. **现代宽带技术**:随着宽带技术的发展,网络传输速度提高,使得高清视频和其他大型教学资源在网络上的流畅传输成为可能,极大地丰富了...
附件中的"浅谈“三层结构”原理与用意(附源码)"文档和源码,提供了一个具体的应用实例,可以帮助读者更好地理解和实践三层架构。通过阅读和分析这些材料,开发者能够掌握如何在ASP.NET+C#环境中运用三层架构进行...
在快速发展的软件行业中,课程的目标应与学生的职业岗位能力相匹配,包括程序设计、Windows应用开发、Web应用开发、用户界面设计、数据库应用与开发以及组件开发能力。 课程内容应以职业岗位需求为导向,注重...