免费特效素材网站,wordpress mysql备份,怎么制作网站logo,长链接生成短链接网址作者#xff1a;zuoxiaolong8810#xff08;左潇龙#xff09;#xff0c;转载请注明出处#xff0c;特别说明#xff1a;本博文来自博主原博客#xff0c;为保证新博客中博文的完整性#xff0c;特复制到此留存#xff0c;如需转载请注明新博客地址即可。 模板方法模… 作者zuoxiaolong8810左潇龙转载请注明出处特别说明本博文来自博主原博客为保证新博客中博文的完整性特复制到此留存如需转载请注明新博客地址即可。 模板方法模式这是一个在许多优秀的开源项目中LZ见的最多的一个设计模式也是LZ觉得最为优秀的一个设计模式所以这一章LZ会尽自己所能的去尽量将这个设计模式解释清楚。 模板方法模式一般是为了统一子类的算法实现步骤所使用的一种手段或者说是方式。它在父类中定义一系列算法的步骤而将具体的实现都推迟到子类。 最典型的形式就是一个接口一个抽象父类父类中会有一系列的抽象方法而在子类中去一一实现这些方法。 下面LZ给举一个例子比如我们有一个接口里面就一个方法是用来制造一个HTML页面如下。 public interface PageBuilder {String bulidHtml();} 这个接口很简单就是直接制造一个Html页面的内容假设我们不使用模板方法模式直接让各个子类去直接实现这个接口那么肯定实现的方式千奇百怪而且步骤也乱七八糟的这样实在不利于维护和扩展。所以我们可以使用模板方法模式将这个过程给制定好然后把具体的内容填充交给子类就好这样这些子类生成的HTML页面就会非常一致。 基于这个目的我们定义如下抽象类去实现这个接口并且我们定义好步骤。 public abstract class AbstractPageBuilder implements PageBuilder{private StringBuffer stringBuffer new StringBuffer();public String bulidHtml() {//首先加入doctype,因为都是html页面,所以我们父类不需要推迟给子类实现,直接在父类实现stringBuffer.append(!DOCTYPE html PUBLIC \-//W3C//DTD XHTML 1.0 Transitional//EN\ \http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\);//页面下面就是成对的一个HTML标签我们也在父类加入,不需要给子类实现stringBuffer.append(html xmlns\http://www.w3.org/1999/xhtml\);//下面就应该是head标签里的内容了,这个我们父类做不了主了,推迟到子类实现,所以我们定义一个抽象方法,让子类必须实现appendHead(stringBuffer);//下面是body的内容了我们父类依然无法做主仍然推迟到子类实现appendBody(stringBuffer);//html标签的关闭stringBuffer.append(/html);return stringBuffer.toString();}//第一个模板方法protected abstract void appendHead(StringBuffer stringBuffer);//第二个模板方法protected abstract void appendBody(StringBuffer stringBuffer);} 上面LZ已经加了注释这下我们如果要制作一个html页面就直接继承我们的抽象父类就可以了而我们的子类只需要实现两个模板方法就可以成功完成html页面的创建下面LZ给出一个子类我们随意制造一个html页面。 public class MyPageBuilder extends AbstractPageBuilder{Overrideprotected void appendHead(StringBuffer stringBuffer) {stringBuffer.append(headtitle你好/title/head);}Overrideprotected void appendBody(StringBuffer stringBuffer) {stringBuffer.append(bodyh1你好,世界/h1/body);}public static void main(String[] args) {PageBuilder pageBuilder new MyPageBuilder();System.out.println(pageBuilder.bulidHtml());}} 我们简单的加入一个head和body标签然后创建测试类运行一下就会发现我们按照父类给的标准模板生成了一个html页面。 这样做的方式的好处是父类可以规范子类的创建过程便于我们维护而且子类也更省事因为像doctype包括html标签都是一样的所以子类不再需要关心这些。当然上述LZ写的有点粗糙其实我们可以定义的更仔细一点比如head标签里第一个是title然后是meta等等。但作为例子我们还是遵循简单的原则主要还是想给各位传达模板方法模式的思想。 模板方法模式是所有设计模式当中LZ觉得最无侵入性的模式因为它的好处实在是太明显了。模板方法模式并不强制接口的实现类必须继承所以不会对子类造成任何影响而如果子类的实现可以配得上模板类的模板那么就可以享受模板方法模式带来的好处。 通常情况下模板方法模式用于定义构建某个对象的步骤与顺序或者定义一个算法的骨架。 我们刚才的示例明显就是构建一个String对象的过程在这里要声明一点对于模板方法模式父类提供的构建步骤和顺序或者算法骨架通常是不希望甚至是不允许子类去覆盖的所以在某些场景中可以直接将父类中提供骨架的方法声明为final类型。 模板方法模式还有一种使用的方式为了给子类足够的自由度可以提供一些方法供子类覆盖去实现一些骨架中不是必须但却可以有自定义实现的步骤。 比如上述的例子当中我们应该都知道HTML页面中有一些标签是可有可无的。比如meta标签link标签script标签等。那么我们可以将刚才的例子细化一下去看一下上面说的供子类覆盖的方法是什么。我们将刚才的抽象父类细化成如下形式。 public abstract class AbstractPageBuilder implements PageBuilder{private static final String DEFAULT_DOCTYPE !DOCTYPE html PUBLIC \-//W3C//DTD XHTML 1.0 Transitional//EN\ \http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\;private static final String DEFAULT_XMLNS http://www.w3.org/1999/xhtml;private StringBuffer stringBuffer new StringBuffer();public String bulidHtml() {stringBuffer.append(DEFAULT_DOCTYPE);stringBuffer.append(html xmlns\ DEFAULT_XMLNS \);stringBuffer.append(head);appendTitle(stringBuffer);appendMeta(stringBuffer);appendLink(stringBuffer);appendScript(stringBuffer);stringBuffer.append(/head);appendBody(stringBuffer);stringBuffer.append(/html);return stringBuffer.toString();}protected void appendMeta(StringBuffer stringBuffer){}protected void appendLink(StringBuffer stringBuffer){}protected void appendScript(StringBuffer stringBuffer){}protected abstract void appendTitle(StringBuffer stringBuffer);protected abstract void appendBody(StringBuffer stringBuffer);} 可以看到我们将head标签的生成过程更加细化了分成四个方法titlemetalink和script。但是这四个里面appendTitle是模板方法子类必须实现而其它三个则是普通的空方法。 那么上述三个方法就是留给子类覆盖的当然子类可以选择不覆盖那么生成的HTML就没有metalink和script这三种标签如果想有的话就可以覆盖其中任意一个比如下面这样。 public class MyPageBuilder extends AbstractPageBuilder{protected void appendMeta(StringBuffer stringBuffer) {stringBuffer.append(meta http-equiv\Content-Type\ content\text/html; charsetutf-8\ /);}protected void appendTitle(StringBuffer stringBuffer) {stringBuffer.append(title你好/title);}protected void appendBody(StringBuffer stringBuffer) {stringBuffer.append(body你好世界/body);}public static void main(String[] args) {PageBuilder pageBuilder new MyPageBuilder();System.out.println(pageBuilder.bulidHtml());}} 我们覆盖了appendMeta方法所以我们就可以在head标签中生成一个meta标签。如果各位看过上章的适配器模式其实这里和缺省适配很像目的都是一样的因为如果把appendMeta也写成抽象方法那么子类就必须实现但是meta标签又不是必须的所以子类就有可能把appendMetaappendLinkappendScript方法全空着了。 所以为了不强制子类实现不必要的抽象方法但又不剥夺子类自由选择的权利我们在父类提供一个默认的空实现来让子类自由选择是否要覆盖掉这些方法。 说到模板方法模式我们JDK当中有一个类与它还有一个不得不说的故事那就是类加载器。 JDK类加载器可以大致分为三类分别是启动类加载器扩展类加载器以及应用程序加载器。 这三者加载类的路径分别为如下 启动类加载器JAVA_HOME/lib目录下以及被-Xbootcalsspath参数设定的路径不过启动类加载器加载的类是有限制的如果JVM不认识的话你放在这些目录下也没用。 扩展类加载器JAVA_HOME/lib/ext目录下以及被java.ext.dirs系统变量指定的路径。 应用程序类加载器用户自己的类路径classpath这个类加载器就是我们经常使用的系统类加载器并且JDK中的抽象类ClassLoader的默认父类加载器就是它。 在这里为什么说类加载器和模板方法模式有关呢是因为ClassLoader类就使用了模板模式去保证类加载过程中的唯一性。LZ先给各位看下这个类当中的模板模式的应用。 public abstract class ClassLoader {//这是一个重载方法public Class? loadClass(String name) throws ClassNotFoundException {return loadClass(name, false);}//这里就是父类算法的定义protected synchronized Class? loadClass(String name, boolean resolve)throws ClassNotFoundException{Class c findLoadedClass(name);if (c null) {try {if (parent ! null) {c parent.loadClass(name, false);} else {c findBootstrapClass0(name);}} catch (ClassNotFoundException e) {c findClass(name);}}if (resolve) {resolveClass(c);}return c;}//这里留了一个方法给子类选择性覆盖protected Class? findClass(String name) throws ClassNotFoundException {throw new ClassNotFoundException(name);}} LZ截取了主要的部分为了突出这三个方法。在上面LZ加了简单的注释相信经过刚才的介绍各位应该能看出来这是一个模板方法模式只是它没有定义抽象方法因为findClass这个方法并不是必须实现的所以JDK选择留给程序员们自己选择是否要覆盖。 从代码上我们可以看出在ClassLoader中定义的算法顺序是。 1首先看是否有已经加载好的类。 2如果父类加载器不为空则首先从父类类加载器加载。 3如果父类加载器为空则尝试从启动加载器加载。 4如果两者都失败才尝试从findClass方法加载。 这是JDK类加载器的双亲委派模型即先从父类加载器加载直到继承体系的顶层否则才会采用当前的类加载器加载。这样做的目的刚才已经说了是为了JVM中类的一致性。 如果有读者第一次接触这方面的知识估计会比较迷茫下面LZ给出一个例子。各位猜测下下面程序的运行结果会是什么 package com.classloader;public class ClassLoaderTest {public static void main(String[] args) throws Exception {Class? clazz ClassLoader.getSystemClassLoader().loadClass(com.classloader.ClassLoaderTest);Object entity clazz.newInstance();System.out.println(entity instanceof ClassLoaderTest);}
} 相信各位都可以毫无疑问的猜测出来结果应该是true这是因为entity是ClassLoaderTest类的一个实例instanceof关键字用来判断一个实例是否属于一个特定的类型所以结果就是true。 那么各位再来猜猜下面这段代码的运行结果会是什么 package com.classloader;import java.io.IOException;
import java.io.InputStream;class MyClassLoader extends ClassLoader{public Class? loadClass(String name) throws ClassNotFoundException {String fileName name.substring(name.lastIndexOf(.)1) .class;InputStream is getClass().getResourceAsStream(fileName);if (is null) {return super.loadClass(name);}try {byte[] b new byte[is.available()];is.read(b);return defineClass(name, b, 0, b.length);} catch (IOException e) {throw new ClassNotFoundException();}}}public class ClassLoaderTest {public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {ClassLoader classLoader new MyClassLoader();Class? clazz classLoader.loadClass(com.classloader.ClassLoaderTest);Object entity clazz.newInstance();System.out.println(entity instanceof ClassLoaderTest);}} 对于类加载器比较熟悉的读者们可能觉得这个结果并不出乎意料可是或许还是有人会比较意外为什么结果会是false呢 这是因为如果没有按照ClassLoader中提供的骨架算法去加载类的话可能会造成JVM中有两个一模一样的类信息他们是来自一个类文件但却不是一个加载器加载的所以这两个类不相等。 这也是类加载器为何要使用模板模式给我们定义好查找的算法是为了保证我们加载的每一个类在虚拟机当中都有且仅有一个。 不过你可能会想既然如此为何不把loadClass方法写成final类型的这样不是更安全吗 这是因为有的时候我们希望JVM当中每一个类有且仅有一个但有的时候我们希望有两个甚至N个就比如我们的tomcat你可以想象下你每一个项目假设都有com.xxx.xxxx.BaseDao等等如果这些类都是一个的话你的tomcat还能同时启动多个WEB服务吗虽说tomcat也是遵循的双亲委派模型但是从此也可以看出来我们并不是在所有时候都希望同一个全限定名的类在整个JVM里面只有一个。 这里提到类加载器是为了给模板方法一个现有的现实中的例子以便于有些看多了自己制造的例子的读者可以换个口味如果有机会LZ会在这个系列完结以后专门开一个系列来和各位分享学习虚拟机过程中的感悟本次不再过多介绍类加载器的相关内容。 另外如果多掌握一些类加载器的知识还是对平时的工作和学习有很大帮助的各位也可以私下去搜索下相关资料。 好了模板方法模式就介绍到这里吧希望各位都有自己的收获。 谢谢观看。 下期预告装饰器模式。 版权声明 作者zuoxiaolong左潇龙 出处博客园左潇龙的技术博客--http://www.cnblogs.com/zuoxiaolong 您的支持是对博主最大的鼓励感谢您的认真阅读。 本文版权归作者所有欢迎转载但未经作者同意必须保留此段声明且在文章页面明显位置给出原文连接否则保留追究法律责任的权利。 转载于:https://www.cnblogs.com/Zyf2016/p/6337740.html