本来想写点spring相关的东西的,想来想去,先写点设计模式的东西吧

  什么是设计模式?套用百度百科的话解释吧

  设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

  这么说,新人应该很难捉摸,大体的说,设计模式就是设计代码结构,方便开发或者以后的调整,学习java,应该没有不会常用设计模式的吧?

  设计模式遵循开闭原则,对拓展开放,对修改关闭,就是说,我们一个功能写好使用之后,如果要为这个功能进行拓展,那尽量不要去修改它的代码,而采用一些其他的方式去实现它,而这些其他的方式,就是设计模式。

  java常用的设计模式大致分为3类总共有二三十种吧,但是真正进行应用开发使用的不多,如果是做框架方面的,可以多了解一下,肯定是有帮助的,所以这里就只介绍应用开发常用的八种设计模式

一、工厂模式

  工厂模式应该都很熟悉,就是创建对象用的,看下面的例子:

package com.example;

public interface Mobile {
public void play();
public void call();
}
package com.example;

public class Android implements Mobile {

    public void play() {
// TODO Auto-generated method stub
System.out.println("Android play");
} public void call() {
// TODO Auto-generated method stub
System.out.println("Android call");
}
}

  首先,我们有个手机的接口Mobile,手机可以打电话,可以玩游戏,接着有个Android实现了手机接口,它实现了打电话玩游戏的功能

package com.example;

public class MobileFactory {
public static Mobile create() {
return new Android();
}
}

  接着,我们创建一个工厂,用工厂创建Android手机类打电话,玩游戏,测试一下

package com.example;

public class Test {

    public static void main(String[] args) {
// TODO Auto-generated method stub
Mobile mobile = MobileFactory.create();
mobile.call();// 打电话
mobile.play();// 玩游戏
} }

  结果输出:

  

  如果哪天业务需求变了,不使用Android手机了,要使用IPhone手机,那么我们只需用IPhone类实现Mobile接口,然后修改工厂的创建功能即可

package com.example;

public class IPhone implements Mobile {

    public void play() {
// TODO Auto-generated method stub
System.out.println("IPhone call");
} public void call() {
// TODO Auto-generated method stub
System.out.println("IPhone call");
} }
package com.example;

public class MobileFactory {
public static Mobile create() {
return new IPhone();
}
}

  然后同样使用上面的测试得到结果

  

  使用工厂模式就是提醒我们,写代码的时候,尽可能的避免使用new关键字创建对象,因为new关键字意味着依赖,如果一个类到处都是使用new关键字实例化,那么哪天这个类不用了,要换成其它的实现类,我们就需要到处修改,这样就太消耗时间了

  再者,我们上面的列子中,工厂MobileFactory中的创建方法是静态方法,当然我们也可以换成一般的对象方法,只不过我们调用时需要创建对象去调用方法,但是可以在实例化工厂对象的时候往构造函数中传入一些参数,从而可以对创建的对象进行一些配置或者改造,另外,根据面向接口开发的思想,我们也可以为这个工厂创建一个接口,需要实例化的工厂只需实现接口的就可以了

  还有,工厂可以有多个创建方法,创建方法可以带参数,如上面的例子,如果需求中,我们Android类和IPhone都需要使用到,我们可以在create方法中传入一个参数,用于指定需要实例化的是Android类还是IPhone类,或者直接创建两个方法,一个用于实例化Android类,一个实例化IPhone类,如:  

package com.example;

public class MobileFactory {
public static Mobile create(int flag) {
if (flag == 1) {
return new Android();
} else {
return new IPhone();
}
}
}

  或者

package com.example;

public class MobileFactory {
public static Mobile createAndroid() {
return new Android();
} public static Mobile createIPhone() {
return new IPhone();
}
}

  总之,工厂模式就是提醒我,当需要实例化对象时,我们尽可能避免使用new去实例化,考虑为对象创建一个统一的来源

二、单例模式

  单例模式,表面上很容易理解,就是说这个类型在存在期间只有一个实例,实现方法就是讲构造函数私有化,从而除了类内部,其它地方均不能实例化类的对象,如:

package com.example;

public class Context {
private static Context instance = null; private Context() {
} public synchronized static Context getInstance() {
if (instance == null) {
instance = new Context();
}
return instance;
}
}

  上面的Context类的构造函数被私有化了,因此外面是不能实例化它的,但是为了提供Context类的实例,这里就有了一个getInstance静态方法,外面测试一下:

package com.example;

public class Test {

    public static void main(String[] args) {
// TODO Auto-generated method stub
Context ctx1=Context.getInstance();
Context ctx2=Context.getInstance();
System.out.println(ctx1.equals(ctx2));
} }

  执行结果:

  

  可以看到,上面实例化得到的是同一个对象,细心的朋友可能会看到,在getInstance方法中,外面使用synchronized关键字,这个关键字的作用可以理解为上锁,当一个线程进入方法时,其它线程就需要等待,当然,我们上面的例子里面,如果去掉这个关键字,也是不会报错的,甚至在开发时写单例,发布生产环境也可能不报错,但总归不是线程安全的,可能在一个线程去实例化这个唯一对象时,属性等等还没设置好,另外一个线程又去获取了这个对象,然后获取属性,发现为空,可能就会引发异常

  另外,因为synchronized关键字是修饰方法的,也就是说每次只有一个线程能进这个方法里面去,哪怕是这个唯一实例生成后,这样不免造成一些性能上不好的影响,所以我们可以这样去实现:

package com.example;

public class Context {
private static Context instance = null;private Context() {
} private synchronized static void init() {
if (instance == null) {
instance = new Context();
}
} public static Context getInstance() {
if (instance == null) {
init();
}
return instance;
}
}

  上面的例子中,synchronized还是修饰方法,但是已经不是对外的方法了,所以在某种程度上说,会更好一些。

三、适配器模式

  适配器主要指的是对接口方法进行适配,看下面的例子

package com.example;

public interface Phone {
public void time();
public void call() ;
}
package com.example;

public class Watch {
public void time() {
System.out.println("I am Watch:time");
}
}

  首先,我们有一个电话接口,电话可以看时间,打电话,同时我们有一个手表类,手表只能看时间,如果我们现在要写一个类去实现电话接口,一般需要实现它里面的所有方法,但是我们已经有了Watch类,所以我们可以将Watch类用上,具体实现如下:   

package com.example;

public class Telephone extends Watch implements Phone {

    public void call() {
// TODO Auto-generated method stub
System.out.println("I am Telephone:call");
}
}

  测试一下调用:

package com.example;

public class Test {

    public static void main(String[] args) {
// TODO Auto-generated method stub
Phone phone=new Telephone();
phone.time();
phone.call();
} }

  结果:  

  

  可以看到,这里我们只实现了Phone的call接口,但是Telephone继承了Watch类,所以它有time方法,而且正好和Phone的time方法是同名同参数的,所以自动适配到了Phone接口的time方法,但是现实开发中,我们往往会碰到方法名或者参数不一样,但是同样功能的方法,我们就只能去显示的实现Phone的time接口了:

package com.example;

public class Telephone implements Phone {

    public void call() {
// TODO Auto-generated method stub
System.out.println("I am Telephone:call");
} public void time() {
// TODO Auto-generated method stub
new Watch().time();
}
}

  另外,如果我们不想让Telephone有父类,或者Telephone有其他的父类要继承,因为java的单继承远程,我们也可以这么使用

  适配器模式可以理解为用已有的方法去实现同功能的接口,从而不至于造成同样的功能几个地方都有。还有,真实开发过程中,我们可以实现类和接口之间添加一些父类,这样可以更利于我们封装一些同功能的方法

四、策略模式

  程序里,我们经常会看到像下面  

     int result=0;
if(flag==1){
//做一些计算得到result
}else if(flag==2){
//做另外一些计算得到result
}else{
//做另外一些计算得到result
}

  如果这里面每个flag要做一些动作,那么这样的代码很不容易理解,而且也不方便拓展。于是我们可以创建一个这些操作的接口,然后每个flag对应的操作封装到不同实现这个接口的类型,比如:

package com.example;

public interface Calculator {
public int exec(int a,int b);
}
package com.example;

public class PlusCalculator implements Calculator {

    public int exec(int a, int b) {
// TODO Auto-generated method stub
return a + b;
} }
package com.example;

public class MinusCalculator implements Calculator {

    public int exec(int a, int b) {
// TODO Auto-generated method stub
return a - b;
} }

  测试使用:

package com.example;

public class Test {

    public static void main(String[] args) {
// TODO Auto-generated method stub int flag = 0;
Calculator calculator = null;
if (flag == 1) {
calculator = new PlusCalculator();
} else {
calculator = new MinusCalculator();
}
int result = calculator.exec(5, 3);
System.out.println(result);
} }

  这里演示的只是简单的加减,但是实际的业务算法一般是比较复杂,代码量比较多的,如果不封装,代码会难看

  当然,我们也可以为这些实现类写个父类,因为一般的,这些实现类都会有一些公共的逻辑  

五、装饰模式

  装饰模式也有人叫修饰模式,其实,装饰模式就是对一些操作增加一些功能,比如在操作之前记录操作日志,操作之后记录结果,

  比如,上面我们定义了一个计算接口,并有两个类实现了接口,加法类PlusCalculator 和减法类MinusCalculator ,正常调用,我们只有结果,到底哪两个参数我们不知道:

package com.example;

public class Test {

    public static void main(String[] args) {
// TODO Auto-generated method stub
Calculator plus = new AddCalculator();
int plusResult = plus.exec(5, 3);
System.out.println(plusResult); Calculator minus = new MinusCalculator();
int minusResult = minus.exec(5, 3);
System.out.println(minusResult);
} }

  

  现在我们可以创建装饰类,用来记录打印计算的参数:

package com.example;

public class Decorator implements Calculator {
Calculator calculator = null; public Decorator(Calculator calculator) {
this.calculator = calculator;
} public int exec(int a, int b) {
// TODO Auto-generated method stub System.out.println("before:a=" + a + " b=" + b);
int result = calculator.exec(a, b);
System.out.println("after:save result:" + result);
return result;
} }

  我们把测试改一下,执行后得到结果:

package com.example;

public class Test {

    public static void main(String[] args) {
// TODO Auto-generated method stub
Calculator plus = new Decorator(new PlusCalculator());
int plusResult = plus.exec(5, 3);
System.out.println(plusResult); Calculator minus = new Decorator(new MinusCalculator());
int minusResult = minus.exec(5, 3);
System.out.println(minusResult);
} }

  

  这样,我们就记录到了参数信息,这种做法可以用法记录操作日志和结果,简单的说,装饰模式就是拓展一个类型的功能,而不是去方法调用处一个个去加,如果接口的某些方法不想用了,可以抛出提示

六、代理模式

  代理模式在形式上和装饰模式有点像,但是和装饰模式的功能不一样,装饰模式更像修饰,修饰一个 接口,然后实现接口的所有类都能被修饰到,所以装饰的对象一般都是接口,而代理模式充当的是中介者的角色,你要访问必须通过类A去访问类B,所以代理的对象一般都是真实的类对象,比如上面的加法类和减法类,我们可以创建他们的代理:  

package com.example;

public class PlusProxy implements Calculator {
PlusCalculator calculator; public PlusProxy() {
calculator = new PlusCalculator();
} public int exec(int a, int b) {
// TODO Auto-generated method stub
System.out.println("before:a=" + a + " b=" + b + " a+b=?");
int result = calculator.exec(a, b);
System.out.println("after:a+b=" + result);
return result;
} }
package com.example;

public class MinusProxy implements Calculator {

    MinusCalculator calculator;

    public MinusProxy() {
calculator = new MinusCalculator();
} public int exec(int a, int b) {
// TODO Auto-generated method stub
System.out.println("before:a=" + a + " b=" + b + " a-b=?");
int result = calculator.exec(a, b);
System.out.println("after:a-b=" + result);
return result;
} }

  然后我们使用代理类去操作,而不是直接对用对应的计算类操作:

package com.example;

public class Test {

    public static void main(String[] args) {
// TODO Auto-generated method stub
Calculator plus = new PlusProxy();
int plusResult = plus.exec(5, 3);
System.out.println(plusResult); Calculator minus = new MinusProxy();
int minusResult = minus.exec(5, 3);
System.out.println(minusResult);
} }

  结果输出:

  

  可以这么说,装饰模式和代理模式差不多,只是他们作用对象的区别

七、模板设计模式

  这个设计模式可以这么理解,把通用性的东西设置好,关键的部分放空,或者设置一个默认操作,然后使用模板是,只需要重写关键部分就可以了,换成类的方式去说就是,使用一个父类设计好模板,放出一些方法供子类实现或重写,比如:

package com.example;

public abstract class People {
protected abstract String getName(); protected abstract String getWord(); public void say() {
System.out.println(getName() + ":" + getWord());
}
}

  我们有一个People类,它有三个方法,但是有两个方法没有具体实现,而是留给子类去实现,我们写两个类去实现它  

package com.example;

public class Teacher extends People {

    @Override
protected String getName() {
// TODO Auto-generated method stub
return "老师";
} @Override
protected String getWord() {
// TODO Auto-generated method stub
return "上课";
} }
package com.example;

public class Student extends People {

    @Override
protected String getName() {
// TODO Auto-generated method stub
return "学生";
} @Override
protected String getWord() {
// TODO Auto-generated method stub
return "老师好";
} }

  我们测试调用:

package com.example;

public class Test {

    public static void main(String[] args) {
// TODO Auto-generated method stub
People teacher=new Teacher();
teacher.say(); People student=new Student();
student.say(); } }

  

八、观察者模式

  观察者模式有点像回调函数,有点像订阅,就是先订阅,当状态发生变化时再通知,比如:

package com.example;

public interface Observer {
public void send();
}

  首先,我们有一个观察者接口,它有一个通知方法,就是通知订阅对象,QQ用户和微信用户都可以实现这个接口

package com.example;

public class QQObserver implements Observer {

    public void send() {
// TODO Auto-generated method stub
System.out.println("QQ received");
} }
package com.example;

public class WxObserver implements Observer {

    public void send() {
// TODO Auto-generated method stub
System.out.println("Wx received");
} }

  现在,观察者有两种类型,QQ用户和微信用户,然后我们可以定义一个博客类

package com.example;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; public class Blog {
List<Observer> list=new ArrayList<Observer>(); public void add(Observer obs) {
list.add(obs);
} public void publish() {
System.out.println("new blog published"); Iterator<Observer> ite= list.iterator();
while (ite.hasNext()) {
Observer obs=ite.next();
obs.send();
}
}
}

  博客可以通过add方法添加订阅对象,当博客发布后,就会去通知这些订阅的对象,我们测试一下:

package com.example;

public class Test {

    public static void main(String[] args) {
// TODO Auto-generated method stub
Observer qq=new QQObserver();
Observer wx=new WxObserver(); Blog blog=new Blog();
blog.add(qq);//qq用户订阅
blog.add(wx);//微信用户订阅
blog.publish();//博客发布
}
}

  

  

  总结:设计模式有很多种,但都是一些理解性的东西,自己动手敲敲代码能更好的理解,每种设计模式都特定的用处,要看具体情况具体看。不要认为设计模式很麻烦就不注重它,无数的经验告诉我们,一个好的程序设计能节省我们大量的时间和精力

  

Java常用的几种设计模式的更多相关文章

  1. java:常用的两种设计模式(单例模式和工厂模式)

    一.单例模式:即一个类由始至终只有一个实例.有两种实现方式(1)定义一个类,它的构造方法是私有的,有一个私有的静态的该类的变量在初始化的时候就实例化,通过一个公有的静态的方法获取该对象.Java代码  ...

  2. 关于W3Cschool定义的设计模式-常用的9种设计模式的介绍

    一.设计模式 tip:每种设计模式,其实都是为了更高效的,更方便的解决在面对对象编程中所遇到的问题. 什么是设计模式:     是一套经过反复使用.多人知晓的.经过分类的.代码设计经验的总结   为什 ...

  3. [设计模式](转)Java中的24种设计模式与7大原则

    *:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...

  4. Java编程的23种设计模式

    设计模式(Design Patterns)                                   --可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用. ...

  5. PHP常用的三种设计模式

    本文为大家介绍常用的三种php设计模式:单例模式.工厂模式.观察者模式,有需要的朋友可以参考下. 一.首先来看,单例模式 所谓单例模式,就是确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实 ...

  6. java常用的几种线程池比较

    1. 为什么使用线程池 诸如 Web 服务器.数据库服务器.文件服务器或邮件服务器之类的许多服务器应用程序都面向处理来自某些远程来源的大量短小的任务.请求以某种方式到达服务器,这种方式可能是通过网络协 ...

  7. JAVA开发的23种设计模式之 --- 桥接模式

    桥接模式 概述:将抽象部分与他的实现部分分离,这样抽象化与实现化解耦,使他们可以独立的变化.如何实现解耦的呢,就是通过提供抽象化和实现化之间的桥接结构.    应用场景        实现系统可能有多 ...

  8. Java常用的几种线程池

    常用的几种线程池 5.1 newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程. 这种类型的线程池特点是: 工作线程的创 ...

  9. PHP常用的 五种设计模式及应用场景

    设计模式六大原则 开放封闭原则:一个软件实体如类.模块和函数应该对扩展开放,对修改关闭. 里氏替换原则:所有引用基类的地方必须能透明地使用其子类的对象. 依赖倒置原则:高层模块不应该依赖低层模块,二者 ...

随机推荐

  1. Linux安装软件出错

    1.Delta RPMs disabled because /usr/bin/applydeltarpm not installed. yum provides '*/applydeltarpm' # ...

  2. 测试数据库并发压力的shell脚本

    本节内容:一例用于测试数据库并发压力的shell脚本代码. 例子: #!/bin/bash #********************************# #并发后台运行fun # #for w ...

  3. 【Java 8】Stream.distinct() 列表去重示例

    在这篇文章里,我们将提供Java8 Stream distinct()示例. distinct()返回由该流的不同元素组成的流.distinct()是Stream接口的方法. distinct()使用 ...

  4. Spring boot 数据源配置。

    配置文件 : spring boot  配置文件 有两种形式 ,一种是properties文件.一种是yml文件.案列使用properties文件. 数据源的默认配置 : spring boot 约定 ...

  5. 一行配置搞定 Spring Boot项目的 log4j2 核弹漏洞!

    相信昨天,很多小伙伴都因为Log4j2的史诗级漏洞忙翻了吧? 看到群里还有小伙伴说公司里还特别建了800+人的群在处理... 好在很快就有了缓解措施和解决方案.同时,log4j2官方也是速度影响发布了 ...

  6. Hibernate框架使用之环境搭建

    第一步:引入所需的jar包 第二步:创建实体类,配置实体类与数据表的映射关系 创建实体类 User.java package cn.hao.entity; public class User { /* ...

  7. UVA12412 师兄帮帮忙 A Typical Homework (a.k.a Shi Xiong Bang Bang Mang) 题解

    Content 自己去看题面去. Solution 算不上很繁琐的一道大模拟. 首先,既然是输出 \(0\) 才退出,那么在此之前程序应当会执行菜单 \(\Rightarrow\) 子操作 \(\Ri ...

  8. JAVA开发 环境基础 IDEA 常用快捷键

    java 源代码运行必须先用javac编译生成字节码文件 XXX.class运行 java XXX 进行运行 环境变量classpath:已编译的字节码文件搜索路径--临时配置: set classp ...

  9. org.apache.jasper.runtime.ELContextImpl cannot be cast to org.apache.jasper.el.ELContextImpl

    org.apache.jasper.runtime.ELContextImpl cannot be cast to org.apache.jasper.el.ELContextImpl错误怎么解决: ...

  10. JAVA比较两个版本号的大小

    /** * 比较版本号的大小 (两个版本号格式应尽量相同) * * @param v1 版本号1 * @param v2 版本号2 * @return 正数:v1大 负数:v2大 0:相等 */ pu ...