一.创建型模式
1.单例模式
(1)核心作用
保证一个类只有一个实例,并且提供一个访问该实例的全局访问点
(2)常见使用场景
①Windows的Task Manager(任务管理器)就是很典型的单例模式
②Windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例
③项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取
④网站的计数器,一般也是采用单例模式实现,否则难以同步。
⑤应用程序的日志应用,一般都采用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加
⑥数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源
⑦操作系统的文件系统,也是单例模式实现的具体例子,一个操作系统只能有一个文件系统
⑧Application也是单例的典型应用(Servlet编程中会涉及到)
⑨在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理
⑩在servlet编程中,每个Servlet也是单例
⑪在SpringMVC框架/strus1框架中,控制器对象也是单例的
(3)单例模式的优点
①由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决
②单例模式可以在系统设置全局的访问点,优化共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理
(4)常见的五种单例模式实现方式
主要:
①饿汉式(线程安全,调用效率高。不能延时加载)
②懒汉式(线程安全,调用效率不高。可以延时加载)
其他:
①双重检锁式(由于JVM底层内部模型原因,偶尔会出现问题。不建议使用)
②静态内部类式(线程安全,调用效率高。但是,可以延时加载)
③枚举单例(线程安全,调用效率高,不能延时加载)
1.1 饿汉式
//饿汉式单例模式 public class SingletonDemo1 { // 类初始化时,立即加载这个对象(没有延时加载的优势)。加载类时,天然线程安全 private static SingletonDemo1 instance = new SingletonDemo1(); private SingletonDemo1() {//私有化构造器 } // 方法没有同步,调用效率高 public static SingletonDemo1 getInstance() { return instance; } }
1.2 懒汉式
//懒汉式单例设计模式 public class SingletonDemo2 { // 类初始化时,不初始化这个对象(延时加载,真正用的时候再创建) private static SingletonDemo2 s; private SingletonDemo2() { } //方法同步,调用效率低 public static synchronized SingletonDemo2 getInstance() { if (s == null) { s = new SingletonDemo2(); } return s; } }
2.工厂模式
(1)实现了创建者和调用者的分离
(2)分类:
①简单工厂模式
用来生产同一等级结构中的任意产品(对于增加新的产品,需要修改已有代码)
②工厂方法模式
用来生产同一等级结构中的固定产品(支持增加任意产品)
③抽象工厂模式
用来生产不同产品族的全部产品(对于增加新的产品,无能为力;支持增加产品族)
(3)面向对象设计的基本原则
①OCP(开闭原则):一个软件的实体应当对扩展开放,对修改关闭
②DIP(依赖倒转原则):要针对接口编程,不要针对实现编程
③LoD(迪米特法则):只与你直接的朋友通信,而避免和陌生人通信
(3)核心本质
①实例化对象,用工厂方法代替new操作
②将选择实现类、创建对象统一管理和控制。从而将调用者跟我们的实现类解耦
(4)工厂模式要点
①简单工厂模式(静态工厂模式)
虽然某种程度不符合设计原则,但实际使用最多
②工厂方法模式
不修改已有类的前提下,通过增加新的工厂类来实现扩展
③抽象工厂模式
不可以增加产品,可以增加产品族
(5)应用场景
①JDK中Calendar的getInstance方法
②JDBC中Connection对象的获取
③Hibernate中SessionFactory创建Session
④Spring中IOC容器创建管理bean对象
⑤XML解析时的DocumentBuilderFactory创建解析器对象
⑥反射中Class对象的newInstance()
2.1 简单工厂模式
(1)要点
①简单工厂模式也叫静态工厂模式,工厂类一般是使用静态方法,通过接收的参数的不同来返回不同的
对象实例
②对于增加新的产品无能为力!不修改代码的话,是无法扩展的。
(2)实现代码
//接口Car public interface Car { void run(); } // Audi类 public class Audi implements Car{ @Override public void run() { System.out.println("奥迪在跑"); } } // Byd类 public class Byd implements Car { @Override public void run() { System.out.println("比亚迪在跑"); } } //CarFactory工厂 public class CarFactory { // 方法1 public static Car createCar(String type) { if ("奥迪".equals(type)) { return new Audi(); } else if ("比亚迪".equals(type)) { return new Byd(); } else { return null; } } // 方法2 // public static Car createAudi(){ // return new Audi(); // } // public static Car createByd(){ // return new Byd(); // } } // 测试 public class Client { public static void main(String[] args) { Car c1 = CarFactory.createCar("奥迪"); Car c2 = CarFactory.createCar("比亚迪"); c1.run(); c2.run(); } }
2.2 工厂方法模式
(1)要点
①为了避免简单工厂模式的缺点,不完全满足OCP
②工厂模式和简单工厂模式最大的不同在于,简单工厂模式只有一个(对于一个项目或者一个独立模块而言)
工厂类,而工厂方法模式一组实现了相同接口的工厂类
(2)代码
//接口Car public interface Car { void run(); } // Audi类 public class Audi implements Car{ @Override public void run() { System.out.println("奥迪在跑"); } } // Byd类 public class Byd implements Car { @Override public void run() { System.out.println("比亚迪在跑"); } } //CartFactory工厂接口 public interface CarFactory { Car createCar(); } //AudiFactory工厂类 public class AudiFactory implements CarFactory{ @Override public Car createCar() { return new Audi(); } } //BydFactory工厂类 public class BydFactory implements CarFactory{ @Override public Car createCar() { return new Byd(); } } //测试 public class Client { public static void main(String[] args) { Car c1 = new AudiFactory().createCar(); Car c2 = new BydFactory().createCar(); c1.run(); c2.run(); } }
(3)简单工厂模式和工厂方法模式比较
①结构复杂度
从这个角度比较,显然简单工厂模式要占优。简单工厂模式只需要一个工厂类,而工厂方法模式的工厂类随着产品类
的个数增加而增加,这个无疑会使类的个数越来越多,从而增加结构的复杂程度
②代码复杂度
代码复杂度和结构复杂度是一对矛盾,既然简单工厂模式在结构方面相对简洁,那么它在代码方面肯定是比工厂方法
模式复杂的了。简单工厂模式的工厂类随着产品类的增加需要增加很多方法(或代码),而工厂方法模式每个具体工厂类
只完成单一任务,代码简洁
③客户端编程难度
工厂方法模式虽然在工厂类结构中引入了接口从而满足OCP,但是在客户端编码中需要对工厂类进行实例化。而简单工
厂模式的工厂类是个静态类,在客户端无需实例化。而简单工厂模式的工厂类是个静态类,在客户端无需实例化,这无疑
是个吸引人的优点。
④管理上的难度
这是个关键的问题
1.我们先谈扩展,众所周知,工厂方法模式完全满足OCP,即它有非常良好的扩展性。那是否就说明了简单工厂模式就没有扩展
性呢?答案是否定的。简单工厂模式同样具备良好的扩展性——扩展的时候仅需要修改少量的代码(修改工厂类的代码)就可以
满足扩展性的要求了。尽管这没有完全满足OCP,但我们不需要太拘泥于设计理念,要知道,sun提供的Java官方工具包中也有
很多没有满足OCP的例子。
2.然后我们从维护性的角度分析。假如某个具体产品类需要进行一定的修改,很可能需要修改对应的工厂类。当同时需要修改多个
产品类的时候,对工厂类的修改会变得相当麻烦。反而简单工厂没有这些麻烦,当多个产品类需要修改时,简单工厂模式仍然
仅仅需要修改唯一的工厂类
根据设计理念建议:工厂方法模式。但实际上,我们一般都用简单工厂模式
2.3.抽象工厂模式
(1)要点
①用来生产不同产品族的全部产品(对于增加新的产品,无能为力;支持增加产品族)
②抽象工厂模式是工厂模式的升级版本,在有多个业务品种、业务分类时,通过抽象工厂模式产生需要的对象是一种非常好的
解决方式。
(2)代码
//Engine接口 public interface Engine { void run(); void start(); } //LuxuryEngine类 class LuxuryEngine implements Engine { @Override public void run() { System.out.println("转的快"); } @Override public void start() { System.out.println("启动快!可以自动启停"); } } // LowEngine类 class LowerEngine implements Engine { @Override public void run() { System.out.println("转的慢"); } @Override public void start() { System.out.println("启动慢!"); } } //Seat接口 public interface Seat { void message(); } //LuxurySeat类 class LuxurySeat implements Seat { @Override public void message() { System.out.println("可以自动按摩"); } } //LowSeat类 class LowSeat implements Seat { @Override public void message() { System.out.println("不能自动按摩"); } } //Tyre接口 public interface Tyre { void revolve(); } //LuxuryTyre类 class LuxuryTyre implements Tyre{ @Override public void revolve() { System.out.println("旋转不磨损"); } } //LowTyre类 class LowTyre implements Tyre{ @Override public void revolve() { System.out.println("容易磨损"); } } //CartFactory工厂接口 public interface CartFactory { Engine createEngine(); Seat createSeat(); Tyre createTyre(); } //LuxuryFactory工厂实现类 public class LuxuryCarFactory implements CartFactory { @Override public Engine createEngine() { return new LuxuryEngine(); } @Override public Seat createSeat() { return new LuxurySeat(); } @Override public Tyre createTyre() { return new LuxuryTyre(); } } //LowCarFactory工厂实现类 public class LowCarFactory implements CartFactory{ @Override public Engine createEngine() { return new LowerEngine(); } @Override public Seat createSeat() { return new LowSeat(); } @Override public Tyre createTyre() { return new LowTyre(); } } //测试 public class Client { public static void main(String[] args) { LuxuryCarFactory factory = new LuxuryCarFactory(); Engine e = factory.createEngine(); e.run(); e.start(); } }
3.建造者模式
(1)场景
①我们要建造一个复杂的产品。比如:神舟飞船,IPhone。这个复杂的产品的创建。有这样一个问题需要处理:
装配这些子组件是不是有个步骤问题?
②实际开发中,我们所需要的对象构建时,也是非常复杂,有很多步骤需要处理。
(2)构造模式的本质
①分离了对象子组件的单独构建(由Builder来负责)和装配(由Director负责)。从而可以构造出复杂的对象。这个
模式适用于:某个对象的构建过程复杂的情况下使用
②由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配
顺序也可以做出不同的对象。也就是实现了构建算法,装配算法的解耦,实现了更好的复用。
(3)开发中的应用场景
①StringBuilder类的append方法
②SQL中的PreparedStatement
③JDOM中,DemoBuilder、SAXBuilder
(4)代码
//宇宙飞船类 public class AirShip { private OrbitalModule orbitalModule;//轨道舱 private Engine engine;//发动机 private EscapeTower escapeTower;//逃逸塔 public void launch(){ System.out.println("发射成功!!!");} // 属性orbitalModule, engine,escapeTower的Get和Set方法 class OrbitalModule { private String name; //实现Get/Set方法 public OrbitalModule(String name) { this.name = name; } } class Engine { private String name; public Engine(String name) { this.name = name; } //属性name的Get/Set方法 } class EscapeTower { private String name;// 实现Get/Set方法 public EscapeTower(String name) { this.name = name; } } package com.builder; //AirShipBuilder构建接口 public interface AirShipBuilder { Engine builderEngine(); OrbitalModule builderOrbitalModule(); EscapeTower builderEscapeTower(); } //”构建“者实现类 public class SxtAirShipBuilder implements AirShipBuilder { @Override public Engine builderEngine() { System.out.println("构建发动机"); return new Engine("发动机"); } @Override public OrbitalModule builderOrbitalModule() { System.out.println("构建轨道舱"); return new OrbitalModule("轨道舱"); } @Override public EscapeTower builderEscapeTower() { System.out.println("构建逃逸塔"); return new EscapeTower("逃逸塔"); } } //AirShipDirector装配接口 public interface AirShipDirector { // 组装飞船对象 AirShip directAirShip(); } //”装配“实现类 public class SxtAirshipDirector implements AirShipDirector{ private AirShipBuilder builder;//构建者对象 public SxtAirshipDirector(AirShipBuilder builder) { this.builder = builder; } @Override public AirShip directAirShip() { Engine e = builder.builderEngine(); OrbitalModule o = builder.builderOrbitalModule(); EscapeTower et = builder.builderEscapeTower(); AirShip ship = new AirShip(); ship.setEngine(e); ship.setOrbitalModule(o); ship.setEscapeTower(et); return ship; } } //测试 public class Client { public static void main(String[] args) { AirShipDirector director = new SxtAirshipDirector(new SxtAirShipBuilder()); AirShip ship = director.directAirShip(); ship.launch(); } }
4.原型模式
(1)基本概述
①通过new产生一个对象需要非常繁琐的数据准备或访问权限,我们可以使用原型模式
②Java中的克隆技术,以某个对象为原型,复制出新的对象。显然,新的对象具备原型对象的特点
优势有:效率高(直接克隆,避免了重新执行构造过程步骤)
③克隆类似于new,但是不同于new。new创建新的对象属性采用的是默认值。克隆出的对象的属性值完全
和原型对象相同。并且克隆出的新对象改变后不会影响原型对象。然后,再修改克隆对象的值。
(2)原型模式的实现
①实现Cloneable接口,调用Object中的clone方法
②Prototype模式中实现起来最困难的地方就是内存复制操作,所幸在Java中提供了clone()方法替我们做了大部
分事情。
注意用词:克隆和拷贝一回事
(3)代码示例
//①浅克隆 public class Sheep implements Cloneable {//克隆羊 private String name; private Date birthday; // Getter/Setter方法,构造器 @Override protected Object clone() throws CloneNotSupportedException { Object obj = super.clone();//直接调用Object的clone方法 return obj; } } //原型模式测试(浅克隆) public class Client { public static void main(String[] args) throws CloneNotSupportedException { Date date = new Date(2022L); Sheep s1 = new Sheep("少利",date); System.out.println(s1); System.out.println(s1.getName()); System.out.println(s1.getBirthday()); Sheep s2 = (Sheep) s1.clone(); s2.setName("多利"); System.out.println(s2); System.out.println(s2.getName()); System.out.println(s2.getBirthday()); } } // ②深克隆(把属性也进行克隆) public class Sheep2 implements Cloneable { private String name; private Date birthday; @Override protected Object clone() throws CloneNotSupportedException { Object obj = super.clone();//自己调用object对象的clone()方法 // 添加如下代码实现深克隆 Sheep2 s = (Sheep2) obj; s.birthday = (Date) this.birthday.clone();//把属性也进行克隆 return obj; } // Getter/Setter方法,构造器 //原型模式测试(深客隆) public class Client2 { public static void main(String[] args) throws CloneNotSupportedException { Date date = new Date(2023L); Sheep2 s1 = new Sheep2("小利", date); Sheep2 s2 = (Sheep2) s1.clone(); System.out.println(s1); System.out.println(s1.getName()); System.out.println("s1的date修改前:"+s1.getBirthday()); date.setTime(20383L);//修改s1的date,不会影响s2 System.out.println("s1的date修改后:"+s1.getBirthday()); s2.setName("大利"); System.out.println(s2); System.out.println(s2.getName()); System.out.println("s2的date:"+s2.getBirthday()); } } public class Sheep implements Serializable {//克隆羊 private String name; private Date birthday; // Getter/Setter方法,构造方法 @Override protected Object clone() throws CloneNotSupportedException { Object obj = super.clone();//直接调用Object的clone方法 return obj; } } //③原型模式测试(使用序列化和反序列化深克隆) public class Sheep implements Serializable { //实现Serializable接口 private String name; private Date birthday; // 构造器,Getter/Setter方法 } // 使用序列化和反序列化实现深克隆 public class Client3 { public static void main(String[] args) throws Exception { Date date = new Date(8888L); Sheep s1 = new Sheep("小羊", date); System.out.println(s1.getBirthday()); // 使用序列化和反序列化实现深克隆 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(s1); byte[] bytes = bos.toByteArray(); ByteArrayInputStream bis = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bis); Sheep s2 = (Sheep) ois.readObject();//克隆好的对象 date.setTime(9999L); System.out.println(s2.getBirthday()); } }
(4)应用场景【通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式】
原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者
spring中bean的创建实际就是两种:单例模式和原型模式(当然,原型模式需要和工厂模式搭建起来)
二.结构型模式
结构性模式核心作用:
是从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题
1.适配器模式
(1)什么是适配器模式?
将一个类的接口转换成客户希望的另一个接口。Adaptor模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作
(2)模式中的角色
①目标接口(Target):客户端所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
②需要适配的类(Adaptee):需要适配的类或适配者类
③适配器(Adaptor):通过包装一个需要适配的对象,把原接口转换成目标接口
(3)生活中的场景
例如:以前的键盘使用ps/2接口,现在使用为USB接口。转接口(适配器)把键盘(需要适配的类)的ps/2接口
转换为 USB接口(目标接口)
//(1)被适配的类,相当于例子中的PS/2接口键盘 public class Adaptee { public void request(){ System.out.println("可以完成ps/2接口的功能!"); } } //(2)目标接口 public interface Target { public void handleReq(); } //(3)适配器 //①类适配器方式 public class Adaptor extends Adaptee implements Target { @Override public void handleReq() { super.request(); } } //②适配器(使用了组合方式) public class Adaptor2 implements Target { private Adaptee adaptee; @Override public void handleReq() { adaptee.request(); } public Adaptor2(Adaptee adaptee) { super(); this.adaptee = adaptee; } } //客户端(相当于例子中的笔记本,只有USB接口) public class Client { public void test1(Target t) { t.handleReq(); } public static void main(String[] args) { Client c = new Client(); Adaptee a = new Adaptee(); // Target t = new Adaptor(); Target t = new Adaptor2(a); c.test1(t); } }
(4)工作中的场景:经常用来做旧系统改造和升级
(5)Java中的场景
①java.io.InputStreamReader(InputStream)
②java.io.OutputStreamWriter(OutputStream)
// 是字节流通向字符流的桥梁,通过适配器把字节流转化为字符流
2.代理模式(Proxy pattern)
(1)核心作用:通过代理,控制对对象的访问,可以详细控制访问某个类/对象的方法,在调用这个方法前做前置
处理,调用这个方法后做后置处理
注意:AOP(面向切面编程)的核心机制就是代理模式
(2)核心角色:
①抽象角色:定义代理角色和真实角色的公共对外方法
②真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用;实现真正的业务逻辑
③代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以添加自己
的操作。将统一的流程控制放到代理角色中处理。
(3)应用场景
①安全代理:屏蔽对真实角色的直接访问
②远程代理:通过代理类处理远程方法调用
③延迟加载:先加载轻量级的代理对象,真正需要加载时再加载真实对象
例如:当你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100MB,在打开文件时不可能将所有
的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开。
(4)开发框架中的应用场景
①struts2中拦截器的实现
②数据库连接池关闭处理
③Hiberate中延时加载的实现
④mybatis中实现拦截器插件
⑤Aspect的实现
⑥spring在AOP的实现(日志拦截,声明式事务处理)
⑦web service
⑧RMI远程方法调用
2.1 静态代理(静态定义代理类)
① 抽象角色
public interface Star { void confer();//面谈 void signContract();//签合同 void bookTicket();//订票 void sing();//唱歌 void collectMoney();//收钱 }
②真实角色
public class RealStar implements Star{ @Override public void confer() { System.out.println("RealStar面谈"); } @Override public void signContract() { System.out.println("RealStar签合同"); } @Override public void bookTicket() { System.out.println("RealStar订票"); } @Override public void sing() { System.out.println("RealStar唱歌"); } @Override public void collectMoney() { System.out.println("RealStar收钱"); } }
③代理角色
public class ProxyStar implements Star { private Star star; public ProxyStar(Star star) { this.star = star; } @Override public void confer() { System.out.println("代理面谈"); } @Override public void signContract() { System.out.println("代理签合同"); } @Override public void bookTicket() { System.out.println("代理订票"); } @Override public void sing() { star.sing();//RealStar唱歌 } @Override public void collectMoney() { System.out.println("代理收钱"); } }
④客户端测试
2.2 动态代理(动态生成代理类)
2.2.1 分类
(1)JDK自带的动态代理
1)java.lang.reflect.Proxy(作用:动态生成代理类和对象)
2)java.lang.reflect.InvocationHandler(处理器接口)
①可以通过invoke方法实现对真实角色的代理访问
②每次通过Proxy生成代理类对象时都要指定对应的代理器对象
(2)JavaScript字节码操作库实现
(3)CGLIB
(4)ASM(底层使用指令,可维护性较差)
2.2.2 优点
抽象角色(接口)声明的方法都被转移到调用处理器中一个集中的方法去处理。这样,我们就可以更加灵活和统一
的处理众多的方法
2.2.3 实例
①抽象对象
public interface Star { void confer();//面谈 void signContract();//签合同 void bookTicket();//订票 void sing();//唱歌 void collectMoney();//收钱 }
②真实对象
public class RealStar implements Star { @Override public void confer() { System.out.println("RealStar面谈"); } @Override public void signContract() { System.out.println("RealStar签合同"); } @Override public void bookTicket() { System.out.println("RealStar订票"); } @Override public void sing() { System.out.println("RealStar唱歌"); } @Override public void collectMoney() { System.out.println("RealStar收钱"); } }