盘一盘那些Java中常用的设计模式
设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。本系列文章,重温Java中常用的设计模式,记录自己的工作经验以及想法,仅供看客参考。
  • 重温Java设计模式——适配器模式   

    前言程序设计模式其实是前人在不断探索过程中总结出来的,符合一定的开闭原则并且相对来说足够优雅的代码范式。经历过足够长的项目洗礼,其实用性自然是不用怀疑。既然是在编码过程中衍化而来的范式,那么我辈在开发过程中也会或多或少的用到,或者说自然而然的写出来。所以,关于Java设计模式的介绍,主要是帮大家规范平时的代码。换句话说,帮助大家缩短从自热而然的初窥门径到熟练运用的所花费时间。至于具体的代码,大家其实不用过于在意,关键是领会其思想,解决了什么问题。 关于适配器模式百度百科对适配器一词的解释: 适配器是英语Adapter/adaptor的汉语翻译。适配器就是一个接口转换器,它可以是一个独立的硬件接口设备,允许硬件或电子接口与其它硬件或电子接口相连,也可以是信息接口。比如:电源适配器、三角架基座转接部件、USB与串口的转接设备等。Java的适配器模式中的适配器,也是起到了一个转换的作用,将目前手头有单不匹配的功能接口转换成适用的目标接口。更专业的解释是: 适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。话不多说,下面记录一些我借鉴一些技术文章并结合自己的想法,而进行的一些测试。![图片](https://oomabc.com/staticsrc/img/201906/21/1561088076827de97c411b3d9476b8d35ab82e426b2c6.jpg) 电脑电源适配器我先根据我们最常接触的电源适配器来进行抽象,应用下适配器模式。首先定义一个供电电源接口类PowerSource.java:Javapackage designpatterns.adapter.e3;/ @author Smile.Wu @version 2015-10-19 电源接口 /public interface PowerSource { int supplyPower();}定义一个需要使用电源进行工作的笔记本电脑接口类Computer.java:Javapackage designpatterns.adapter.e3;/ @author Smile.Wu @version 2015-10-19 /public interface Computer { boolean startUp();}定义一个对应笔记本电脑的虚拟类,实现了Computer类的接口,增加了电压校验的方法AbstractComputer.java:Javapackage designpatterns.adapter.e3;/ @author Smile.Wu @version 2015-10-19 /public abstract class AbstractComputer implements Computer { //定义具体电脑的目标电压 public abstract int getTargetVoltage(); / 电脑的启动入口 @param source @return @throws PowerVoltageException / public void startUp(PowerSource source) throws PowerVoltageException { //启动之前,先检查电压,如果是符合要求的稳定电压,则开始启动 if(checkPowerVoltage(source.supplyPower())) { //具体电脑的启动流程 startUp(); } } //电压检查,当前电压和电脑目标电压 public boolean checkPowerVoltage(int powerVoltage) throws PowerVoltageException { if(getTargetVoltage() powerVoltage) { return true; } throw new PowerVoltageException("dangerous voltage for computer[voltage220V]!! you may need a power adapter!"); }}接下来分别定义了在两种电压下正常工作的具体电脑,220V电脑Computer220V.java,210V电脑Computer110V.java:Javapackage designpatterns.adapter.e3;/ @author Smile.Wu @version 2015-10-19 /public class Computer220V extends AbstractComputer implements Computer { //目标电压是220V的电脑 private int startUpVoltage 220; @Override public boolean startUp() { System.out.println("computer[voltage220V] is starting!!"); System.out.println("[BIOS]check CPU......"); System.out.println("[BIOS]check Disk......"); System.out.println("[BIOS]check Software......"); System.out.println("[BIOS]check Memory......"); return true; } @Override public int getTargetVoltage() { return startUpVoltage; }}Javapackage designpatterns.adapter.e3;/ @author Smile.Wu @version 2015-10-19 /public class Computer110V extends AbstractComputer implements Computer { //目标电压是110V的电脑 private int startUpVoltage 110; @Override public boolean startUp() { System.out.println("computer[voltage110V] is starting!!"); System.out.println("[BIOS]check CPU......"); System.out.println("[BIOS]check Disk......"); System.out.println("[BIOS]check Software......"); System.out.println("[BIOS]check Memory......"); return true; } @Override public int getTargetVoltage() { return startUpVoltage; }}然后实现一个电源,提供220V的电压PowerSource220V.java:Javapackage designpatterns.adapter.e3;/ @author Smile.Wu @version 2015-10-19 /public class PowerSource220V implements PowerSource { @Override public int supplyPower() { //提供220V电压 return 220; }}那么,到此我们来试试220V工况下,220V的电脑能否正常开机:Java//创建一个220V电源PowerSource powerSource220 new PowerSource220V();//启动220V的电脑AbstractComputer computer new Computer220V();computer.startUp(powerSource220);AbstractComputer computer110 new Computer110V();try { //由于没有110V的适配器,这里110V的电脑启动失败 computer110.startUp(powerSource220);} catch (Exception e) { //错误信息打印 e.printStackTrace();}执行结果如下:bashcomputer[voltage220V] is starting!![BIOS]check CPU......[BIOS]check Disk......[BIOS]check Software......[BIOS]check Memory......designpatterns.adapter.e3.PowerVoltageException: dangerous voltage for computer[voltage220V]!! you may need a power adapter!at designpatterns.adapter.e3.AbstractComputer.checkPowerVoltage(AbstractComputer.java:32)at designpatterns.adapter.e3.AbstractComputer.startUp(AbstractComputer.java:20)at designpatterns.adapter.e3.App.main(App.java:20)从结果输出可以看出,220V电压的电脑可以正常启动,而110V电脑启动失败,因为电压不匹配。 电源适配器在只有220V电源的情况下,我们如何让110V的电脑也正常工作?其实生活中这种情况比比皆是。我们的手机充电机插头通常很大,因为它的主要作用就是变压,将我们生活中通用的220V电压变压到我们手机可以正常充电的值。所以,我们需要一个变压器(也就是这里的电源适配器),将现有的220V电压进行转换,降压为110V。定义一个PowerSourceAdapter.java:Javapackage designpatterns.adapter.e3;/ @author Smile.Wu @version 2015-10-19 /public class PowerSourceAdapter extends PowerSource220V implements PowerSource { private int targetVoltage 110; public PowerSourceAdapter(int targetVoltage) { super(); this.targetVoltage targetVoltage; } @Override public int supplyPower() { //现有电源的电压 int sourceVoltage super.supplyPower(); //如果当前的电源电压不是目标电压,则进行变压适配 if(sourceVoltage ! targetVoltage) { //电压适配:变压 sourceVoltage targetVoltage; } return sourceVoltage; }}PowerSourceAdapter.java类继承了原有的电源,使自己具备了电源提供能力,然后实现目标接口PowerSource.java(这里目标接口其实没变,之所以还是实现了它,是为了更好的说明下面一个例子)。这样,在PowerSourceAdapter.java中就可以利用当前仅有的电源提供的电压,进行一个降压,然后输出。然后再次尝试启动110V电脑:JavaAbstractComputer computer110 new Computer110V();//创建110V的电源适配器PowerSource powerSourceAdapterFor110V new PowerSourceAdapter(110);//通过适配器的作用,成功启动110V的电脑computer110.startUp(powerSourceAdapterFor110V);输入如下:Javacomputer[voltage110V] is starting!![BIOS]check CPU......[BIOS]check Disk......[BIOS]check Software......[BIOS]check Memory......经过适配器的工作,110V的电脑也正常开机。上面这个例子其实和真正的适配器模式没啥关系,因为待适配的接口和目标接口根本就是同一个。不过由此来进行一个过渡还是可以的。 手机充电器在目前只有220V电源的情况下,我们要给手机充电,但是手机的充电接口和电脑完全不同,因此我定义了一个手机充电电源接口,低压供电电源LowPowerSource.java:Javapackage designpatterns.adapter.e3;public interface LowPowerSource { int getChargeSupply(); //获取电压}定义一个手机接口Phone.java,里面只有一个方法:充电。Javapackage designpatterns.adapter.e3;public interface Phone { void charge(LowPowerSource powerSource);}然后我们获得一个手机ApplePhone.java实现了Phone.java的充电接口,参数就是低压供电电源LowPowerSource.java :Javapackage designpatterns.adapter.e3;public class ApplePhone implements Phone { @Override public void charge(LowPowerSource powerSource) { if(powerSource.getChargeSupply() 36) { System.out.println("apple phone is in charge!"); } else { throw new RuntimeException("voltage error of power source!"); } }}由定义可知,目前我们的手机需要的是36V电压,而当前电源是220V,和上面的思路一样,我们需要一个适配器进行降压即可。待适配的接口就是PowerSource.java,目标接口就是LowPowerSource.java:Javapackage designpatterns.adapter.e3;public class ApplePowerSourceAdapater extends PowerSource220V implements LowPowerSource { @Override public int getChargeSupply() { //实现目标接口 //获得目标电源的电压 int powerSource220 super.supplyPower(); //进行一个适配处理,也就是降压 powerSource220 36; //输入降压之后的电压 return powerSource220; }}接下来再试验下:JavaApplePhone phone new ApplePhone();phone.charge(new ApplePowerSourceAdapater());结果如下:bashapple phone is in charge!实验成功,说明我们的思路是对的。 其它方式不过,适配器并没有跟待适配接口直接发生联系,而是继承了一个已经实现待适配接口的具体类,然后实现了目标接口。其实我们还可以这样做:Javapackage designpatterns.adapter.e3;public class ApplePowerSourceAdapter1 implements LowPowerSource { //直接持有当前唯一的电源 private PowerSource adaptee null; public ApplePowerSourceAdapter1(PowerSource adaptee) { super(); this.adaptee adaptee; } @Override public int getChargeSupply() { //获得待适配的接口数据 int powerSource220 adaptee.supplyPower(); //进行一个适配处理 powerSource220 36; return powerSource220; }}这个适配器持有了待适配接口的具体实现,通用性更强,它可以适配任意需要适配的电源。实验结果如下:Javaphone.charge(new ApplePowerSourceAdapater1(powerSource220));bashapple phone is in charge! 结束语上面提到的两种适配方式,其实我也不是很清楚具体的优劣,尤其是在实际场景中的优劣。不过大家也没必要纠正宗的适配器模式到底是哪一种,抑或是第三种。设计模式与我们而言,是提升工作效率的一种有效途径,切忌深入模式不可自拔。只要能提升编码效率、降低维护成本、提高扩展性,都是好的模式。说不定,你就能创出自己的设计模式。以上这些是我最近对于适配器模式的思考,如果有啥不准确的地方,大家不要介意!!

    Java   设计模式   适配器   2019-07-01 浏览(1236) 有用(0) 阅读原文>> [原创]
  • 重温Java设计模式——建造者模式   

    零、前言 何为建造者模式建造者模式又名创建者模式,它将复杂对象的构建过程与其表示进行分离,通过抽象构建方法将创建不同复杂对象的具体实现延迟到子类。 依赖倒置原则 依赖倒置原则(Dependence Inversion Principle )就是细节要依赖于抽象,不要抽象依赖于细节。简单的说就是面向抽象编程而不是面向实现编程,这样就降低了使用者与实现模块间的耦合。 面向过程的开发,上层调用下层,上层依赖于下层,当下层剧烈变化时,上层也要跟着变化,这就会导致模块的复用性降低而且大大提高了开发和维护的成本。 面向对象的开发很好的解决了这个问题,一般的情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变化,只要抽象不变,客户程序就不需要变化。这大大降低了客户程序域实现细节的耦合度。百科上对于建造者模式是这么描述的: Java23种设计模式之一,英文叫Builder Pattern。 其核心思想是将一个“复杂对象的构建算法”与它的“部件及组装方式”分离,使得构件算法和组装方式可以独立应对变化;复用同样的构建算法可以创建不同的表示,不同的构建过程可以复用相同的部件组装方式。 优缺点在创建者模式中,客户端不再负责对象的创建与组装,而是把这个对象创建的责任交给其具体的创建者类,把组装的责任交给组装类,客户端只负责对象的调用,从而明确了各个类的职责。当对象构建的细节需要变化时,只要构建过程的抽象定义不发生变化,则客户端是不需要进行修改的,这就是依赖倒置原则,通过解耦构建(抽象)和表示(实现)来得到较好的扩展性。虽然利用创建者模式可以创建出不同类型的产品,但是如果产品之间的差异巨大,则需要编写多个创建者类才能实现,这种情况,结合工厂模式可能会更好。--- 一、建造者模式的组成部分通常情况下,一个完整的构建者模式至少包含四种角色:1. 建造者角色(Builder):抽象了复杂对象的创建过程,它定义了这个对象中各个部分的构建规范,但没有具体实现,只是一个抽象的接口。2. 具体建造者角色(ConcreteBuilder):它需要实现Builder定义的所有接口,针对不同的业务需求具体实现接口逻辑;并在完成构建过程后,提供一个复杂对象的实例。3. 指导者(Director):调用ConcreteBuilder来构建复杂对象的各个部分,它没有具体的产品信息以及任何构建细节,但是它保证了复杂对象各个部件的构造顺序和完整性。4. 产品(Product):构建的复杂对象,通常会包含多个部件或者属性。整个设计模式就是为了得到她。 建造者模式和工厂模式1. 工厂方法模式中,客户端通过具体工厂类的createXXX()方法来获得对象实例。相对来说,抽象的工厂类通常会定义某一类产品的一个抽象方法,适合简单对象的创建。如果是复杂对象的创建,或者说创建过程本身会比较复杂,可能包含了一定的限制逻辑的情况下,建造者模式会比工厂方法模式更适合。2. 抽象工厂模式从代码结构上看,可能与建造者模式比较类似。Builder中定义了buildAAA()、builderBBB()、buildCCC()、buildDDD()等接口,而抽象工厂方法类中也定义了类似createAAA()、createBBB()、createCCC()、createDDD()等接口,然后由不同的子类去实现这些方法。不过,他们还有有比较大的区别: 1. 抽象工厂模式关注的是某一个系列的产品的创建,目的是保证不同系列的产品不会混淆创建,而且每个系列的产品都是完整的。 2. 建造者模式关注的是复杂产品的创建流程(产品的组成部分、创建顺序),它的目的是在不改变创建流程的情况下实现复杂产品类型的扩展。 3. 抽象工厂模式创建的是一系列的若干个产品,而建造者模式最终只创建了由许多部件组成的单一产品。--- 二、代码示例/模仿一遍代码 定义Product类Product就是我们需要通过建造者模式来构建的复杂对象,我在网上看了许多例子,各种各样的Product都有,在这里,我将交通工具作为我们本次例子中需要构建的复杂对象。交通工具本身的组成部分非常复杂,我这就简单抽象了几个比较重要发部分,比如车架(整体框架结构)、车轮(依赖行驶的脚)、发动机(广义上的动力来源,也可以是人力或者动物)、座位(交通工具承运的容器)。java/ @author wjyuian 交通工具类 /public class Vehicle { //轮胎描述 private String wheels; //发动机描述 private String engine; //座位描述 private String seats; //车架描述 private String carriage; public String getWheels() { return wheels; } public void setWheels(String wheels) { this.wheels wheels; } public String getEngine() { return engine; } public void setEngine(String engine) { this.engine engine; } public String getSeats() { return seats; } public void setSeats(String seats) { this.seats seats; } public String getCarriage() { return carriage; } public void setCarriage(String carriage) { this.carriage carriage; } public Vehicle() { } public String vehicleInfo() { StringBuilder b new StringBuilder(); b .append("车轮:").append(getWheels()).append("\n") .append("车架:").append(getCarriage()).append("\n") .append("发动机:").append(getEngine()).append("\n") .append("座椅:").append(getSeats()).append("\n") ; return b.toString(); }}接着定义一个Vehicle的builder接口类,它抽象了Vehicle各个组成部件的构建方法:javapublic interface IVehicleBuilder { //组装轮胎 2 void buildWheels(); //组装引擎 3 void buildEngine(); //安装座位 4 void buildSeats(); //安装车架 1 void vuildCarriage(); //创建汽车 Vehicle buildVehicle();}我定义了三类交通工具的具体类,分别是公交车、SUV和跑车,它们都实现了IVehicleBuilder的接口:javapublic class BusBuilder implements IVehicleBuilder { private Vehicle vehicle; public BusBuilder() { vehicle new Vehicle(); } @Override public void buildWheels() { vehicle.setWheels("335/70 R35 X 8"); } public void buildEngine() { vehicle.setEngine("插电式混合动力发动机,峰值扭矩800N·m"); } @Override public void buildSeats() { vehicle.setSeats("整车45个座位,20个站位"); } @Override public void vuildCarriage() { vehicle.setCarriage("非承载式车身"); } @Override public Vehicle buildVehicle() { return vehicle; }}public class SuvBuilder implements IVehicleBuilder { private Vehicle vehicle; public SuvBuilder() { vehicle new Vehicle(); } @Override public void buildWheels() { vehicle.setWheels("235/60 R18 X 4"); } @Override public void buildEngine() { vehicle.setEngine("2.0T 双涡轮增压发动机,2017年世界十佳发动机"); } @Override public void buildSeats() { vehicle.setSeats("超大空间,七座"); } @Override public void vuildCarriage() { vehicle.setCarriage("非承载式车身"); } @Override public Vehicle buildVehicle() { return vehicle; }}public class RacingCarBuilder implements IVehicleBuilder{ private Vehicle vehicle; public RacingCarBuilder() { vehicle new Vehicle(); } @Override public void buildWheels() { vehicle.setWheels("前:345/40 R20 X 2;后:315/45 R20 X 2"); } @Override public void buildEngine() { vehicle.setEngine("5.8L 自然吸气发动机"); } @Override public void buildSeats() { vehicle.setSeats("两座双人跑车"); } @Override public void vuildCarriage() { vehicle.setCarriage("承载式车身"); } @Override public Vehicle buildVehicle() { return vehicle; }}最后重点来了,建造者模式的核心角色就是director,它可不是“导演”,叫它指挥官会更合适。它负责按照预定义的构建流程和规则来对builder提供的方法进行调用,最终给出一个构造完毕的复杂对象。它本身不参与任何部件的构造细节,只负责构建动作的调度和组合,所以我叫它指挥官。javapublic class VehicleDirector { public Vehicle buildVehicle(IVehicleBuilder builder) { if(builder null) { throw new IllegalArgumentException("builder 不能为空"); } //先安装车身 builder.vuildCarriage(); //装上轮子 builder.buildWheels(); //装上发动机 builder.buildEngine(); //最后装上座椅 builder.buildSeats(); return builder.buildVehicle(); }}惯例来一个测试类:javapublic class BuilderTest { public static void main(String[] args) { VehicleDirector director new VehicleDirector(); Vehicle bus director.buildVehicle(new BusBuilder()); System.out.println(bus.vehicleInfo()); System.out.println(); Vehicle suv director.buildVehicle(new SuvBuilder()); System.out.println(suv.vehicleInfo()); System.out.println(); Vehicle racingCar director.buildVehicle(new RacingCarBuilder()); System.out.println(racingCar.vehicleInfo()); }}测试结果:Shell车轮:335/70 R35 X 8车架:非承载式车身发动机:插电式混合动力发动机,峰值扭矩800N·m座椅:整车45个座位,20个站位车轮:235/60 R18 X 4车架:非承载式车身发动机:2.0T 双涡轮增压发动机,2017年世界十佳发动机座椅:超大空间,七座车轮:前:345/40 R20 X 2;后:315/45 R20 X 2车架:承载式车身发动机:5.8L 自然吸气发动机座椅:两座双人跑车--- 三、再来一个例子总感觉前面的那个例子随大流,少了一股味道,码农的味道。不甘如此的我,执着的想了一个晚上,终于想出了一个很适合自己的例子。其实就是换汤不换药,下面直接给出代码。 Project定义javapublic class WebProject { private String projectInfo; private String dataBaseModule;//数据层框架描述 private String dataAccessModule;//数据访问交互层框架、描述 private String accessControlModule;//访问控制层框架、描述 private String viewModule;//展现层框架、描述 public WebProject(String info) { this.projectInfo info; } / 中间省略了getter、setter / public String projectDetail() { StringBuilder b new StringBuilder(); b .append("项目:").append(getProjectInfo()).append("\n") .append("数据存储层:").append(getDataBaseModule()).append("\n") .append("数据访问层:").append(getDataAccessModule()).append("\n") .append("访问控制层:").append(getAccessControlModule()).append("\n") .append("展现层:").append(getViewModule()).append("\n") ; return b.toString(); } } Bulder接口类java//定义复杂对象中各个部件的构建方法public interface IWebProjectBuilder { //选择并配置数据存储层软件 void buildDataBaseModule(); //选择并整合数据访问层框架 void buildDataAccessModule(); //选择并配置访问控制层框架 void buildAccessControlModule(); //选择并匹配展现层框架 void buildViewModule(); //最终返回构建完成的对象 WebProject buildWebProject();} 复杂产品构建类根据自己多年java开发经验,找了相对有代表性的两个j2ee框架进行举例,第三个例子是本博客的实现框架,突出说明复杂产品之间组成部件相似,但是细节不同的特点。第一个是SSH框架风靡的时候:java//曾经红极一时的 j2ee web开发框架public class SSHWebProjectBuilder implements IWebProjectBuilder { private WebProject sshProject null; public SSHWebProjectBuilder() { sshProject new WebProject("SSH项目"); } @Override public void buildDataBaseModule() { sshProject.setDataBaseModule("选择开源的mySQL作为数据存储软件"); } @Override public void buildDataAccessModule() { sshProject.setDataAccessModule("数据访问层通过Spring实现依赖注入;Hibernate作为ROM映射框架,降低mySQL数据读取的复杂度"); } @Override public void buildAccessControlModule() { sshProject.setAccessControlModule("访问控制层采用Struts框架"); } @Override public void buildViewModule() { sshProject.setViewModule("展现层采用简单的JSP"); } @Override public WebProject buildWebProject() { return this.sshProject; }}第二个是SpringMVC和myBatis兴起的时候:java//后起之秀SpringMVC和myBatispublic class SSMWebProjectBuilder implements IWebProjectBuilder { private WebProject sshProject null; public SSMWebProjectBuilder() { sshProject new WebProject("SSM项目"); } @Override public void buildDataBaseModule() { sshProject.setDataBaseModule("选择开源的mySQL作为数据存储软件"); } @Override public void buildDataAccessModule() { sshProject.setDataAccessModule("数据访问层通过Spring实现依赖注入;myBatis作为ROM映射框架,降低mySQL数据读取的复杂度,同时更容易进行自定义SQL开发"); } @Override public void buildAccessControlModule() { sshProject.setAccessControlModule("访问控制层采用SpringMVC框架,安全性、并发性都更好"); } @Override public void buildViewModule() { sshProject.setViewModule("展现层采用简单Freemark"); } @Override public WebProject buildWebProject() { return this.sshProject; }}第三个则是本博客的实现:java//综合考虑之下,本博客采用的框架public class OneBlogProjectBuilder implements IWebProjectBuilder { private WebProject sshProject null; public OneBlogProjectBuilder() { sshProject new WebProject("本博客项目"); } @Override public void buildDataBaseModule() { sshProject.setDataBaseModule("由于服务器资源限制和博客搜索功能要求,采用solr作为数据存储框架"); } @Override public void buildDataAccessModule() { sshProject.setDataAccessModule("数据访问层使用了Solr官方提供的java客户端solrj"); } @Override public void buildAccessControlModule() { sshProject.setAccessControlModule("访问控制层则采用了目前更流行的SpringMVC"); } @Override public void buildViewModule() { sshProject.setViewModule("展现层采用简单的JSP+JSTL+jQuery"); } @Override public WebProject buildWebProject() { return this.sshProject; }} Director并没有什么不同javapublic class WebProjectDirector { public WebProject buildWebProject(IWebProjectBuilder builder) { builder.buildDataBaseModule(); builder.buildDataAccessModule(); builder.buildAccessControlModule(); builder.buildViewModule(); return builder.buildWebProject(); }} 通过相似的测试代码进行测试,结果如下:Shell项目:SSH项目数据存储层:选择开源的mySQL作为数据存储软件数据访问层:数据访问层通过Spring实现依赖注入;Hibernate作为ROM映射框架,降低mySQL数据读取的复杂度访问控制层:访问控制层采用Struts框架展现层:展现层采用简单的JSP项目:SSM项目数据存储层:选择开源的mySQL作为数据存储软件数据访问层:数据访问层通过Spring实现依赖注入;myBatis作为ROM映射框架,降低mySQL数据读取的复杂度,同时更容易进行自定义SQL开发访问控制层:访问控制层采用SpringMVC框架,安全性、并发性都更好展现层:展现层采用简单Freemark项目:本博客项目数据存储层:由于服务器资源限制和博客搜索功能要求,采用solr作为数据存储框架数据访问层:数据访问层使用了Solr官方提供的java客户端solrj访问控制层:访问控制层则采用了目前更流行的SpringMVC展现层:展现层采用简单的JSP+JSTL+jQuery

    设计模式   建造者模式   Java   2019-07-17 浏览(2767) 有用(0) 阅读原文>> [原创]
  • 重温Java设计模式——工厂模式   

    零、关于设计模式百度百科如是描述:设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。个人拙见,设计模式就是有经验的程序员在重复解决相似问题的过程中,因“偷懒”的天性而总结出来的一种代码风格。经过一代代程序员的完善和总结,使之更加符合软件设计的几大原则,才形成了现在所谓的23种设计模式。今天介绍的工厂模式相关内容,也不全是在下原创,其中一部分相关解释、定义都是网上搜寻而来,我只是做了一个整理归纳。本篇中工厂模式分为三个应用阶段,层层递进,描述如有不妥还请见谅。--- 一、简单工厂模式 简单工厂模式又称静态工厂方法模式。重命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。 它由三个部分组成: 1. 工厂类角色:这是本模式的核心,含有简单的商业逻辑和判断逻辑,用来创建实际对象实例。 2. 抽象产品角色:它一般是具体产品继承的父类或者需要实现的接口。 3. 具体产品角色:工厂类所创建的对象就是此角色的实例,在Java中由一个具体类实现,继承或实现抽象产品角色类。 操作系统可视化界面的编程中,我们需要通过系统创建一些组件,比如按钮、文本输入框或者表格等可视化组件。在进行样式排版的时候通常会用到容器组件,比如横向排版容器、表格排版容器亦或自由定位容器。那么按照简单工程化方法的定义,我们来实践一下这个代码过程。 定义抽象产品角色定义顶层抽象接口,组件:javapublic interface IComponent { //设置组件所属操作系统,标记 void setComponentOs(String os); //获得当前组件所标记的对应的操作系统信息 String getComponentOs(); //组件通用初始化方法 boolean init();}然后定义其中一种组件的接口类,可视化组件:java/ 可视化组件接口类 /public interface IVisibleComponent extends IComponent { //获取组件标识符 String componentName(); //这里可以增加可视化组件特有的相关初始化方法}建议定义一个组件的通用类,放一些公用的方法,默认实现一些接口:java/ 抽象类,公共方法定义 /public abstract class AbstractComponent implements IVisibleComponent { private String componentName "VisibleComponent"; //组件的定位属性 private int x; private int y; private int width; private int height; private String os; //可视化组件的通用初始化方法 public AbstractComponent(ComponentConfiger conf) { super(); this.x conf.getX(); this.y conf.getY(); this.width conf.getWidth(); this.height conf.getHeight(); setComponentName(conf.getComponentName()); } public boolean isPositionValidate() { return x 0 && y 0 && width 0 && height 0; } protected String printComponentInfo() { return "定位坐标(" + x + ", " + y + "),大小(" + width + ", " + height + ");OS :" + getComponentOs(); } public void setComponentName(String componentName) { this.componentName componentName; } @Override public String componentName() { return this.componentName; } @Override public void setComponentOs(String os) { this.os os; } @Override public String getComponentOs() { return this.os; }} 定义具体产品角色定义一个按钮组件:java/ 按钮组件 继承AbstractComponent是为了使用公共方法; 实现VisibleComponent是为了直接表明身份,其实可以不写,因为抽象类中implements了 /public class ButtonComponent extends AbstractComponent implements IVisibleComponent { private String value;//按钮显示信息 public ButtonComponent(ComponentConfiger conf) { super(conf); if(conf instanceof ButtonConfiger) { ButtonConfiger btc (ButtonConfiger) conf; this.value btc.getValue(); } } @Override public boolean init() { if(isPositionValidate()) { System.out.println("调用底层OS【" + getComponentOs() + "】接口初始化"); System.out.println(componentName() + "[" + value + "]" + " 初始化成功,信息:" + printComponentInfo()); return true; } System.out.println("组件初始化失败 " + ButtonComponent.class); return false; }}定义一个文本输入框组件:java/ 文本输入框组件 /public class TextInputComponent extends AbstractComponent implements IVisibleComponent { public TextInputComponent(ComponentConfiger conf) { super(conf); } @Override public boolean init() { if(isPositionValidate()) { System.out.println("调用底层OS【" + getComponentOs() + "】接口初始化"); System.out.println(componentName() + " 初始化成功,信息:" + printComponentInfo()); return true; } System.out.println("组件初始化失败 " + TextInputComponent.class); return false; }}这里关于ComponentConfiger的代码就不贴出来了,我只是惯性定义了一个通用配置类,然后实现了不同组件各自不同的配置属性类。比如,通用配置类中关系的是组件的定位坐标和长宽属性;按钮组件配置类除了有通用配置属性,还多出了按钮显示文字属性;表格配置类除了通用属性,还多出了行列数量属性。 定义工厂类角色这里,这个类很简单,只是一个根据不同参数初始化不同的可视化组件而已:javapublic class SimpleComponentFactory { / 初始化一个组件 @param componentType 组件类型 @param conf 组件参数 / public static IVisibleComponent createComponent(ComponentEnum componentType, ComponentConfiger conf) { if(componentType null ) { return null; } switch (componentType) { case BUTTON: return new ButtonComponent(conf); case TABLE: return new TableComponent(conf); case TEXTINPUT: return new TextInputComponent(conf); default: break; } return null; }}最后写一个测试类:javapublic class SimpleFactoryTest { public static void main(String[] args) { ButtonConfiger btnConf new ButtonConfiger(); btnConf.setRectangle(1, 2, 200, 50); btnConf.setComponentName("按钮"); btnConf.setValue("这是一个按钮"); IVisibleComponent button SimpleComponentFactory.createComponent(ComponentEnum.BUTTON, btnConf); button.init(); }}测试结果如下:Shell调用底层OS【null】接口初始化按钮[这是一个按钮] 初始化成功,信息:定位坐标(1, 2),大小(200, 50);OS :null 分析如果我们需要增加一个可视化组件,比如说图片显示组件,那么可以定一个ImageComponent类即可,实现IVisibleComponent接口;但是同时需要修改这个静态工厂方法类,增加一个case项,这显然违背了开闭原则。可想而知,新的可视化组件的加入,这个静态工厂方法类都要做出修改,对于这种工厂方法类,我称之为“上帝类”,因为他控制了所有逻辑,所有组件的加入都要他来负责。由于简单工厂模式只有一个工程类来负责创建所有组件,因此在实际应用中大量的组件会把上帝累坏,其实就是我们码农自己。但是,如果一个组件对应一个工厂,由不同的工厂来创建不同属性的同一类组件,这样就会方便很多。如果增加一个不同类型的组件,我们只要增加一个新的工厂即可,这样就变得可扩展而不用修改原来的代码。于是,就有了第二阶段“工厂方法模式”的出现。--- 二、工厂方法模式它去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承、实现。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。 它主要由四个部分组成: 1. 抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。 2. 具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。 3. 抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。 4. 具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。 这个模式的改进之处是提供了一个用于创建对象的接口(工厂接口),让其实现类(工厂实现类)决定实例化哪一个类(组件),并且由该实现类创建对应类的实例。它可以在一定程度上解耦,组件使用和组件实现类完全隔离,双方只通过抽象接口进行操作,组件的实现细节对组件的使用方是透明的,使用方只要通过工厂接口创建组件即可。也一定程度上增加了扩展性,如果要增加一个图片组件,只要增加图片类和图片工厂类即可。每个类负责的内容更加内聚,可读性也更高,代码结构也更简单。 定义一个抽象工厂类javapublic interface IComponentFactory { IVisibleComponent createComponent(ComponentConfiger conf);}然后依次创建各个组件对应的工厂实现类,也就是具体工厂角色:java//按钮工厂类public class ButtonFactory implements IComponentFactory { @Override public IVisibleComponent createComponent(ComponentConfiger conf) { //创建一个按钮组件 ButtonComponent btn new ButtonComponent(conf); return btn; }}//表格工厂类public class TableFactory implements IComponentFactory { @Override public IVisibleComponent createComponent(ComponentConfiger conf) { TableComponent tableComponent new TableComponent(conf); return tableComponent; }}//文本输入框工厂类public class TextInputFactory implements IComponentFactory { @Override public IVisibleComponent createComponent(ComponentConfiger conf) { TextInputComponent tableComponent new TextInputComponent(conf); return tableComponent; }}组件实现对象依旧使用之前的定义,有了工厂类,然后再写一个静态工厂方法类,方便调用:javapublic class DynamicComponentFactory { / 根据指定的工厂实现类和配置信息,创建一个可视化组件 @param factory 指定的工厂实现类 @param conf 配置信息 @return 可视化组件对象 / public static IVisibleComponent createComponent(IComponentFactory factory, ComponentConfiger conf){ if(factory null conf null) { throw new IllegalArgumentException("参数不能为null"); } return factory.createComponent(conf); }}最后,依旧需要写一个测试类来测试我们的工厂方法模式:javapublic class DynamicFactoryTest { public static void main(String[] args) { ButtonConfiger btnConf new ButtonConfiger(); btnConf.setRectangle(1, 2, 200, 50); btnConf.setComponentName("按钮"); btnConf.setValue("这是一个按钮"); IComponentFactory factory new ButtonFactory(); IVisibleComponent component DynamicComponentFactory.createComponent(factory, btnConf); component.init(); System.out.println(""); TextInputConfiger textInputConfiger new TextInputConfiger(); textInputConfiger.setRectangle(0, 0, 400, 300); textInputConfiger.setComponentName("文本输入框"); DynamicComponentFactory.createComponent(new TextInputFactory(), textInputConfiger).init(); //图片组件,是扩展的一个组件,只要增加图片组件类和对应工厂类即可 System.out.println(""); ImageConfiger imageConfiger new ImageConfiger(); imageConfiger.setRectangle(0, 0, 1024, 768); imageConfiger.setComponentName("图片"); imageConfiger.setSrc("https://oomabc.com/static/img/logonav.png"); DynamicComponentFactory.createComponent(new ImageFactory(), imageConfiger).init(); }}测试输出:Shell调用底层OS【null】接口初始化按钮[这是一个按钮] 初始化成功,信息:定位坐标(1, 2),大小(200, 50);OS :null调用底层OS【null】接口初始化文本输入框 初始化成功,信息:定位坐标(0, 0),大小(400, 300);OS :null调用底层OS【null】接口初始化图片 初始化成功,信息:定位坐标(0, 0),大小(1024, 768);OS :null到目前为止,工厂方法模式比较好的解决了组件创建、组件扩展的问题。既然,前面说了有三个过程,那么这里自然会有另外的问题抛出,而且是这个工厂方法模式无法解决的问题。在前面的测试输出中,我们看到的是OS【null】,因为我们并未考虑可视化组件对应的操作系统。在第一个阶段提到了操作系统可视化界面的编程,所以,可视化组件是基于操作系统的。不同的操作系统底层构建可视化界面的方法是不同的,例如Window风格与Linux(Unix)风格。而且除了可视化组件,前面还提到了容器组件,它与可视化组件一样,初始化方法都与操作系统相关。所以,我们需要针对不同操作系统调用不同的底层来初始化相关组件。 三、抽象工厂方法首先我们先定义容器组件,这里快速列出相关代码:java/ 虚拟组件,这是一个可视化组件的容器,用于整合可视化组件排版 /public interface IPannelComponent extends IComponent { void addVisibleComponent(IVisibleComponent component);}//flow排版格式的容器组件public class FlowLayoutPannel extends AbstractPannel implements IPannelComponent { public FlowLayoutPannel() { } @Override public boolean init() { System.out.println("FlowLayoutPannel 初始化开始"); System.out.println("调用底层OS【" + getComponentOs() + "】接口"); System.out.println("FlowLayoutPannel 初始化完毕!"); return true; } @Override public void addVisibleComponent(IVisibleComponent component) { System.out.println("FlowLayoutPannel 添加一个可视化组件:" + component.componentName() + ",所属OS : " + getComponentOs()); }}/ 这一层抽象,无关系统 /public interface IPannelFactory { IPannelComponent createPannel();}//flow排版的容器组件工厂类public class FlowLayoutPannelFactory implements IPannelFactory { @Override public IPannelComponent createPannel() { return new FlowLayoutPannel(); }}重点在下面,我们需要定一个针对操作系统的工厂类,然后分别实现Windows和Linux相关系统的工厂方法:javapublic interface OSComponetFactory { //创建可视化组件 IVisibleComponent createVisibleComponent(IComponentFactory factory, ComponentConfiger conf); //创建容器组件 IPannelComponent createPannelComponent(IPannelFactory factory); //创建其他}//Windows的抽象方法类public class WindowsComponentFactory implements OSComponetFactory { static final String OS "Windows 10"; @Override public IVisibleComponent createVisibleComponent(IComponentFactory factory, ComponentConfiger conf) { IVisibleComponent component factory.createComponent(conf); component.setComponentOs(OS); return component; } @Override public IPannelComponent createPannelComponent(IPannelFactory factory) { IPannelComponent pannel factory.createPannel(); pannel.setComponentOs(OS); return pannel; }}//Linux的抽象方法类public class LinuxComponentFactory implements OSComponetFactory { static final String OS "Linux 7"; @Override public IVisibleComponent createVisibleComponent(IComponentFactory factory, ComponentConfiger conf) { IVisibleComponent component factory.createComponent(conf); //这里为了举例说明操作系统工厂类的作用,在这里做了区分,实际情况会有不同 component.setComponentOs(OS); return component; } @Override public IPannelComponent createPannelComponent(IPannelFactory factory) { IPannelComponent pannel factory.createPannel(); pannel.setComponentOs(OS); return pannel; }}最后,写一个测试类,对上述的抽象工厂模式进行简单的测试:javapublic class AbstractDynamicFactoryTest { private void createComponents(OSComponetFactory osFactory) { ImageConfiger imageConfiger new ImageConfiger(); imageConfiger.setRectangle(0, 0, 1024, 768); imageConfiger.setComponentName("图片"); imageConfiger.setSrc("https://oomabc.com/static/img/logonav.png"); IVisibleComponent imageComponent osFactory.createVisibleComponent(new ImageFactory(), imageConfiger); imageComponent.init(); System.out.println(""); ButtonConfiger btnConf new ButtonConfiger(); btnConf.setRectangle(1, 2, 200, 50); btnConf.setComponentName("按钮"); btnConf.setValue("这是一个按钮"); IVisibleComponent btnComponent osFactory.createVisibleComponent(new ButtonFactory(), btnConf); btnComponent.init(); System.out.println(""); TextInputConfiger textInputConfiger new TextInputConfiger(); textInputConfiger.setRectangle(0, 0, 400, 300); textInputConfiger.setComponentName("文本输入框"); osFactory.createVisibleComponent(new TextInputFactory(), textInputConfiger).init(); System.out.println(""); IPannelComponent gridPannel osFactory.createPannelComponent(new GridLayoutPannelFactory()); gridPannel.init(); gridPannel.addVisibleComponent(btnComponent); System.out.println(""); IPannelComponent flowPannel osFactory.createPannelComponent(new FlowLayoutPannelFactory()); flowPannel.init(); flowPannel.addVisibleComponent(imageComponent); } @Test public void testwindows() { createComponents(new WindowsComponentFactory()); } @Test public void testlinux() { createComponents(new LinuxComponentFactory()); }}执行testlinux,输出如下:Shell调用底层OS【Linux 7】接口初始化图片 初始化成功,信息:定位坐标(0, 0),大小(1024, 768);OS :Linux 7调用底层OS【Linux 7】接口初始化按钮[这是一个按钮] 初始化成功,信息:定位坐标(1, 2),大小(200, 50);OS :Linux 7调用底层OS【Linux 7】接口初始化文本输入框 初始化成功,信息:定位坐标(0, 0),大小(400, 300);OS :Linux 7GridLayoutPannel 初始化开始调用底层OS【Linux 7】接口GridLayoutPannel 初始化完毕!GridLayoutPannel 添加一个可视化组件:按钮,所属OS : Linux 7FlowLayoutPannel 初始化开始调用底层OS【Linux 7】接口FlowLayoutPannel 初始化完毕!FlowLayoutPannel 添加一个可视化组件:图片,所属OS : Linux 7这里,在操作系统工厂方法里面创建组件的时候,会进行操作系统标记。如果要切换操作系统,只要换一个操作系统的工厂方法实现即可。这里只是简单的概念测试,实际的代码结构会比这个复杂很多,而且例子中很多针对抽象的操作位置也不是很合适。![图片](https://oomabc.com/staticsrc/img/201810/14/1539502899415d8002ace8e1a4043963a3e11b6cea1bb.jpg)

    设计模式   工厂模式   Java   2019-05-06 浏览(2432) 有用(0) 阅读原文>> [原创]
  • blogTest
    分享文章
     
    使用APP的"扫一扫"功能,扫描左边的二维码,即可将网页分享给别人。
    你也可以扫描右边本博客的小程序二维码,实时关注最新文章。