Java中常用的设计模式代码与理解
Java中常用的设计模式代码与理解
一、单例模式
1.饿汉式 (太饿了,类加载的时候就创建实例)
/** * 饿汉式单例模式 */ public class HungrySingleInstance { // 在类加载时生成一个实例 private final static HungrySingleInstance instance = new HungrySingleInstance(); //因为类默认会有一个公有的无参构造,所以用私有的将其覆盖 private HungrySingleInstance(){} //调用此方法,返回类加载时生成的单例对象 public static HungrySingleInstance getInstance(){ return instance; } }
所谓饿汉式单例设计模式,就是将类的静态实例作为该类的一个成员变量,也就是说在 JVM 加载它的时候就已经创建了该类的实例,因此它不会存在多线程的安全问题。
但是提前对实例进行了初始化或者说构造,如果此实例又没有使用到,就会造成资源的浪费。
2.懒汉式 (太懒了,不主动创建实例,当第一次调用时,才创建)
/** * 懒汉式单例模式 */ public class LazySingleInstance { // 在类加载时生成一个引用为null的实例 private static LazySingleInstance instance = null; //因为类默认会有一个公有的无参构造,所以用私有的将其覆盖 private LazySingleInstance(){ } public static LazySingleInstance getInstance(){ //当实例不为空,就直接返回此实例 if(null == instance){ //锁+判断,避免当多个线程同时通过上面那个判断时,创建多个实例,造成安全问题 synchronized (LazySingleInstance.class){ //只有第一次调这个方法时,创建实例 if(null == instance) { instance = new LazySingleInstance(); } } } return instance; } }
懒汉式单例模式就同时解决了效率与安全的问题。
单例模式常用于以下场景:
1.经常创建又销毁的对象;
2.创建需要消耗很多资源,又经常使用的对象;
3.常用来访问数据库或文件的对象。
二、工厂模式
1.简单工厂模式
实例:一个简单的发送短信邮件的工厂模式。
/** * 一个发送接口,用于发送短信或邮件 */ public interface Send { void send(); }
/** * 邮件类实现发送接口,发出邮件 */ public class Mail implements Send{ @Override public void send() { System.out.println("this is a Mail message!"); } }
/** * 短信类实现发送接口 */ public class Sms implements Send{ @Override public void send() { System.out.println("this is a Sms message!"); } }
/** * 发送工厂类 */ public class SendFactory { /** * 根据参数type的不同,生产不同的实例对象 * @param type * @return */ public Send produce(MessageSenderType type){ if(MessageSenderType.SMS.equals(type)){ return new Sms(); }else if(MessageSenderType.MAIL.equals(type)) { return new Mail(); } return null; } }
/** * 发送类型枚举类 */ public enum MessageSenderType { //短信 SMS, //邮箱 MAIL }
/** * 工厂测试类 */ public class FactoryTest { public static void main(String[] args) { SendFactory factory = new SendFactory(); factory.produce(MessageSenderType.SMS).send(); factory.produce(MessageSenderType.MAIL).send(); } }
简单工厂模式当需要增加产品时,要修改源码,破坏ocp原则(对扩展开放,修改封闭)。
2.工厂方法模式
工厂方法模式与简单工厂模式差不多,区别在于工厂方法模式的工厂类,由多个方法组成,每个方法用来生产一个相应的对象。而简单工厂模式,是在一个方法中,根据传入的参数不同也生产不同的对象。所以他们的局限性也都一样。
3.抽象工厂模式
抽象工厂模式是所有形态的工厂模式中最为抽象和一般性的,抽象工厂模式可以向客户端提供一个接口,使得客户端在不必指定产品的具体类型的情况下,创建多个产品族的产品对象。具有比较好的扩展性,但要写较多的类和接口,前两种工厂模式理解上会困难一些。
package abstractFactory; public abstract class Apple implements Fruit { public abstract void get(); } package abstractFactory; public abstract class Banana implements Fruit { public abstract void get(); } package abstractFactory; public interface Fruit { public void get(); } package abstractFactory; public interface FruitFactory { //实例化Apple public Fruit getApple(); //实例化Banana public Fruit getBanana(); } package abstractFactory; public class InnerApple extends Apple { @Override public void get() { System.out.println("长在室内的苹果"); } } package abstractFactory; public class InnerBanana extends Banana { @Override public void get() { System.out.println("长在室内的香蕉"); } } package abstractFactory; public class InnerFruitFactory implements FruitFactory { @Override public Fruit getApple() { return new InnerApple(); } @Override public Fruit getBanana() { return new InnerBanana(); } } package abstractFactory; public class NorthApple extends Apple { @Override public void get() { System.out.println("长在北方的苹果"); } } package abstractFactory; public class NorthBanana extends Banana { @Override public void get() { System.out.println("长在北方的香蕉"); } } package abstractFactory; public class NorthFruitFactory implements FruitFactory { @Override public Fruit getApple() { return new NorthApple(); } @Override public Fruit getBanana() { return new NorthBanana(); } } package abstractFactory; public class SouthApple extends Apple { @Override public void get() { System.out.println("长在南方的苹果"); } } package abstractFactory; public class SouthBanana extends Banana { @Override public void get() { System.out.println("长在南方的香蕉"); } } package abstractFactory; public class SouthFruitFactory implements FruitFactory { @Override public Fruit getApple() { return new SouthApple(); } @Override public Fruit getBanana() { return new SouthBanana(); } } //测试类 package abstractFactory; public class MainClass { public static void main(String[] args) { FruitFactory ff = new NorthFruitFactory(); Fruit apple = ff.getApple(); apple.get(); Fruit banana = ff.getBanana(); banana.get(); System.out.println("~~~~~~~~~~~~~~~~~~~~"); FruitFactory bb = new SouthFruitFactory(); Fruit apple2 = bb.getApple(); apple2.get(); Fruit banana2 = bb.getBanana(); banana2.get(); System.out.println("~~~~~~~~~~~~~~~~~~~~"); //比如要增加室内innerApple,InnerBanana FruitFactory cc = new InnerFruitFactory(); Fruit apple3 = cc.getApple(); apple3.get(); Fruit banana3 = cc.getBanana(); banana3.get(); } }
模式中包含的角色及其职责
1.抽象工厂(Creator)角色
抽象工厂模式的核心,包含对多个产品结构的声明,任何工厂类都必须实现这个接口。(FruitFactory)
2.具体工厂( Concrete Creator)角色
具体工厂类是抽象工厂的一个实现,负责实例化某个产品族中的产品对象。(InnerFruitFactory,NorthFruitFactory,SouthFruitFactory)
3.抽象(Product)角色
抽象模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。(Fruit)
4.具体产品(Concrete Product)角色
抽象模式所创建的具体实例对象(NorthApple,NorthBanana,SouthApple,SouthBanana,InnerBanana,InnerFruitFactory)
总结:抽象工厂中方法对应产品结构,具体工厂对应产品族。
在Java中,其实Spring的IOC就是一种工厂模式,通过SessionFactory来实现依赖注入。在Spring的Bean工厂中,新对象不是通过new关键字来获取的,而是通过先读取配置文件,获取配置文件中配置的类,反射生成对应的对象。
三、代理模式
代理模式是一种常用的设计模式,百度百科中对其定义为:为其他对象提供一个代理以控制对某个对象的访问。在某些情况下,一个对象不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。即代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。代理类与委托类通常会存在关联关系,通常需要实现同一个接口。一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。代理模式的思想是为了提供额外的处理或者不同的操作而在实际对象与调用者之间插入一个代理对象。这些额外的操作通常需要与实际对象进行通信。
1.静态代理
静态代理:由程序员创建或者由第三方工具生成,再进行编译;在程序运行之前,代理类的.class文件已经存在了。静态代理通常只代理一个类,并且要事先知道代理的是什么。示例:假如一个班上的同学要向老师提交作业,但是老师并不直接收每个人的作业,都是通过把作业交给学习委员,再由学习委员将作业转交给老师。在这里面就是学习委员代理班上学生上交作业,学习委员就是学生的代理。
/** * 学生接口 */ public interface Student { //交作业方法 void submitHomework(); }
/** * Person实现学生类,同时也就需要重写学生的交作业方法 */ @Data public class Person implements Student{ private String name; public Person(String name){ this.name = name; } @Override public void submitHomework() { System.out.println(name+"交作业啦!"); } }
/** * 学生的代理实现学生接口 */ @Data public class StudentProxy implements Student{ private Person person; //构造方法,将学生对象委托给代理对象 StudentProxy(Student student){ //student.getClass()为构造方法传入的对象,判断类是不是相同 if(student.getClass() == Person.class){ //相当于学生给与了学习委员他的属性 this.person = (Person) student; } } @Override public void submitHomework() { person.submitHomework(); } }
/** * 测试类 */ public class Test { public static void main(String[] args) { //学生自己交作业 Student student = new Person("槑得说"); student.submitHomework(); //学习委员代交作业 Person person = new Person("有得说"); Student student2 = new StudentProxy(person); student2.submitHomework(); } }
可以看到,在代理类的构造方法中我们先声明了一个学生对象student,然后再将student传给了代理对象person,最后再由person来执行交作业的方法,这就是代理模式。
其实在测试类中也可以看到,学生是可以直接交作业的,而且还简单很多,那为什么还需要代理呢?
其实代理模式呢就是在访问实际对象时,引入了一定的间接性,不直接调用实际对象的方法,而是通过代理对象来完成。这样可以降低系统的耦合性,在上述例子中,如果老师还需要做出评价的话,就可以在不改动源码的情况下,在代理类中完成。
/** * 学生的代理实现学生接口 */ @Data public class StudentProxy implements Student{ private Person zhangsan; //构造方法,将学生对象委托给代理对象 StudentProxy(Student student){ //student.getClass()为构造方法传入的对象,判断类是不是相同 if(student.getClass() == Person.class){ //相当于学生给与了学习委员他的属性 this.zhangsan = (Person) student; } } @Override public void submitHomework() { zhangsan.submitHomework(); System.out.println(zhangsan.getName()+"的作业完成很好!"); } }
代理模式其优点在于:
- 代理模式实现使用者与真实处理者的分离,降低系统的耦合度;
- 调用接口时,便于扩展一些业务无关的其他操作,不影响原系统。
缺点在于:增加类代理角色,性能上比直接使用低。
2.动态代理
Java动态代理的基本原理为:被代理对象需要实现某个接口(这是前提),代理对象会拦截对被代理对象的方法调用,在其中可以全然抛弃被代理对象的方法实现而完成另外的功能,也可以在被代理对象方法调用的前后增加一些额外的功能。动态代理可以为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。