GOF(23种设计模式)分为三大类:
创建型模式:单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
行为模式:模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。
一、创建者模式:都是用来帮助我们创建对象的
1.单例模式
保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
优点:
1)由于单例模式只生成一个实例,减少了系统性能的开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时产生一个单例对象,然后永久驻留内存的方式来解决。
2)由于单例模式可以在系统设置全局的访问点,优化环境共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
常见五种单例模式实现方式:
1)饿汉式(线程安全,调用效率高。但是,不能延时加载。)
/* * 测试饿汉式单例模式 * */ public class SingletonDemo1 { //声明私有静态属性,同时创建对象 //类初始化时,就会加载这个对象(没有延时加载的优势)! // 由于加载类时,天然的是线程安全的 private static SingletonDemo1 instance = new SingletonDemo1(); //构造器私有化 private SingletonDemo1() { } //创建一个对外公开的静态方法访问该静态变量 //方法没有同步调用效率高 public static SingletonDemo1 getInstance() { return instance; } }
2)懒汉式(线程安全,调用效率不高。但是,可以延时加载。)
/* * 测试懒汉式单例模式 * */ public class SingletonDemo2 { //声明私有静态属性 private static SingletonDemo2 instance; //构造器私有化 private SingletonDemo2() { } //创建一个对外公开的静态方法访问该静态变量 //延时加载,懒加载,真正用的时候才加载 //资源效率高了,但是并发时调用效率较低。 // 因为方法synchronized同步,并发时需要等待调用getInstance()方法 public static synchronized SingletonDemo2 getInstance() { if (null == instance) { instance = new SingletonDemo2(); } return instance; } }
3)双重检测锁式(由于编译器优化原因和JVM底层内部模型原因,偶尔会出问题,不建议使用)
/* * 双重检测锁实现单例模式 * */ public class SingletonDemo3 { private static SingletonDemo3 instance = null; private SingletonDemo3() { } public static SingletonDemo3 getInstance() { if (instance == null) { SingletonDemo3 sc; synchronized (SingletonDemo3.class) { sc = instance; if (sc == null) { synchronized (SingletonDemo3.class) { if (sc == null) { sc = new SingletonDemo3(); } } instance = sc; } } } return instance; } }
4)静态内部类式(线程安全,调用效率高,但是,可以延时加载)
/* * 静态内部类实现单例模式(也是一种懒加载方式) * 外部类没有static属性,则不会像饿汉式那样立即加载对象 * */ public class SingletonDemo4 { private SingletonDemo4() { } private static class SingletonClassIntance{ private static final SingletonDemo4 instance = new SingletonDemo4(); } //只有真正调用了getInstance()才会加载静态内部类。 //加载类是线程安全的。 // instance是static final类型,保证了内存中只有一个这样的实例存在,而且只能被赋值一次,从而保证了线程安全性。 //兼并了并发高效调用和延迟加载的优势 public static SingletonDemo4 getInstance() { return SingletonClassIntance.instance; } }
5)枚举单例(线程安全,调用效率高。但是,不能延时加载。)
优点:<1>实现简单
<2>枚举本身就是单例模式。由JVM从根本上提供保证!避免通过反射和反序列化的漏洞
缺点:无延时加载
/* * 使用枚举实现单例模式 枚举类天然是单例的 * 没有延时加载 * */ public enum SingletonDemo5 { //定义一个枚举元素,本身就是单例对象,它就代表了Singleton的一个实例 INSTANCE; //单例可以有自己的操作 public void SingletonOperation() { //功能处理 } }
2.如何选用?
单例对象 占用资源少,不需要 延时加载
枚举式 好于 饿汉式
单例对象 占用资源大,需要 延时加载
静态内部类 好于 懒汉式
问题:
1)反射可以破解上面几种(不包含枚举式)实现方式(可以在构造方法中手动抛出异常控制)
SingletonDemo6 s1 = SingletonDemo6.getInstance(); SingletonDemo6 s2 = SingletonDemo6.getInstance(); System.out.println(s1); System.out.println(s2); //通过反射的方式直接调用私有构造器 Classclazz = (Class ) Class.forName("cn.hyy.test.pattern.singlePattern.SingletonDemo6"); Constructor c = clazz.getDeclaredConstructor(null); c.setAccessible(true); SingletonDemo6 s3 = c.newInstance(); SingletonDemo6 s4 = c.newInstance(); System.out.println(s3); System.out.println(s4);
在构造方法中手动抛出异常控制
/* * 测试懒汉式单例模式(如何防止反射和反序列化漏洞) * */ public class SingletonDemo6 { //声明私有静态属性 private static SingletonDemo6 instance; //构造器私有化 private SingletonDemo6() { if (instance != null) { throw new RuntimeException(); } } //创建一个对外公开的静态方法访问该静态变量 //延时加载,懒加载,真正用的时候才加载 //资源效率高了,但是并发时调用效率较低。 // 因为方法synchronized同步,并发时需要等待调用getInstance()方法 public static synchronized SingletonDemo6 getInstance() { if (null == instance) { instance = new SingletonDemo6(); } return instance; } }
2)反序列化可以破解上面几种(不包含枚举式)实现方式
可以通过定义readResolve()防止获得不同对象
--反序列化时,如果对象所在类定义了readResolve(),(实际是一种回调),定义返回哪个对象。
//通过反序列化方式构造多个对象 FileOutputStream fos = new FileOutputStream("/home/deepin/Desktop/test/aa.txt"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s1); oos.close(); fos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/home/deepin/Desktop/test/aa.txt")); SingletonDemo6 s3 = (SingletonDemo6) ois.readObject(); System.out.println(s3);
/* * 测试懒汉式单例模式(如何防止反射和反序列化漏洞) * */ public class SingletonDemo6 implements Serializable { //声明私有静态属性 private static SingletonDemo6 instance; //构造器私有化 private SingletonDemo6() { if (instance != null) { throw new RuntimeException(); } } //创建一个对外公开的静态方法访问该静态变量 //延时加载,懒加载,真正用的时候才加载 //资源效率高了,但是并发时调用效率较低。 // 因为方法synchronized同步,并发时需要等待调用getInstance()方法 public static synchronized SingletonDemo6 getInstance() { if (null == instance) { instance = new SingletonDemo6(); } return instance; } //反序列化时,如果定义了readResolve(),则直接返回此方法指定的对象。而不需要单独再创建对象! public Object readResolve() throws ObjectStreamException { return instance; } }
多线程环境下五种创建单例模式的效率
CountDownLatch
同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待
countDown() 当前线程调此方法,则计数减一(建议放在finally里执行)
await(),调用此方法会一直阻塞当前线程,直到计时器的值为0
/* * 测试多线程环境下五种创建单例模式的效率 * */ public class Client3 { public static void main(String[] args) throws InterruptedException { long start = System.currentTimeMillis(); CountDownLatch countDownLatch = new CountDownLatch(10); for (int i = 0; i <10 ; i++) { new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 1000000; i++) { Object o = SingletonDemo2.getInstance(); } countDownLatch.countDown(); } }).start(); } countDownLatch.await(); //main线程阻塞,直到计数器变为0,才会继续往下执行 long end = System.currentTimeMillis(); System.out.println("总耗时:" + (end - start)+"秒"); } }
2.工厂模式
实现了创建者和调用者的分离
详细分类:
1)简单工厂模式
2)工厂方法模式
3)抽象工厂模式
面向对象设计的六大基本原则:
SRP(单一职责原则,Single Responsibility Principle):就一个类而言,应该仅有一个引起它变化的原因。简单来说,一个类中应该是一组相关性很高的函数、数据的封装。
OCP(开闭原则,Open-Closed Principle):一个软件的实体应当对扩展开放,对修改关闭
LSP(里式替换原则,Liskov Substitution Principle):所有引用基类的地方必须能透明地使用其子类的对象。通俗点讲,只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必就能适应。
DIP(依赖倒转原则,Dependence Inversion Principle):要针对接口编程,不要针对实现编程
ISP(接口隔离原则,Interface Segregation Principles):客户端不应该依赖它不需要的接口
LoD(迪米特法则,Law of Demeter):只与你直接的朋友通信,而避免和陌生人通信
1)简单工厂模式
也叫静态工厂模式,就是工厂类一般使用静态方法,通过接收的系数的不同来返回不同的对象实例。
对于增加新产品无能为力!不修改代码的话,是无法扩展的(违反开闭原则)
public interface Car { void run(); }
public class Audi implements Car { @Override public void run() { System.out.println("奥迪在跑"); } }
public class Byd implements Car { @Override public void run() { System.out.println("比亚迪在跑"); } }
<1>没有简单工厂的情况下
/* * 没有工厂模式的情况下 * */ public class Client01 { public static void main(String[] args) { Car c1 = new Audi(); Car c2 = new Byd(); c1.run(); c2.run(); } }
UML类图
<2>在有简单工厂的情况下,调用者只需要和工厂打交道
public class CarFactory{ public static Car createCar(String type) { if ("奥迪".equals(type)) { return new Audi(); } else if ("比亚迪".equals(type)) { return new Byd(); } else { return null; } } }
或者
public class CarFactory2 { public static Car createAudi() { return new Audi(); } public static Car createByd() { return new Byd(); } }UML类图
2)工厂方法模式
--为了避免简单工厂的缺点,不完全满足OCP
--工厂方法模式和简单工厂模式最大不同完全在于,简单工厂模式只有一个(对应一个项目或者独立模块而言)工厂类,而工厂方法模式有一组实现了相同接口的工厂类。
public interface Car { void run(); }
public class Audi implements Car { @Override public void run() { System.out.println("奥迪在跑"); } }
public class Byd implements Car { @Override public void run() { System.out.println("比亚迪在跑"); } }
public class Benz implements Car { @Override public void run() { System.out.println("奔驰在跑"); } }
public interface CarFactory { Car createCar(); }
public class AudiFactory implements CarFactory { @Override public Car createCar() { return new Audi(); } }
public class BydFactory implements CarFactory { @Override public Car createCar() { return new Byd(); } }
public class BenzFactory implements CarFactory { @Override public Car createCar() { return new Benz(); } }
public class Client { public static void main(String[] args) { Car c1 = new AudiFactory().createCar(); Car c2 = new BydFactory().createCar(); c1.run(); c2.run(); } }
简单工厂模式和工厂方法模式比较:
1>结构复杂度
简单工厂模式占优,简单工厂模式只需要一个工厂类,而工厂方法模式的工厂类会随着产品类的个数增加而增加,从而增加了结构复杂度。
2>代码复杂度
代码复杂度和结构复杂度是一对矛盾。简单工厂模式在结构方面相对简洁,那么在代码方面肯定比工厂方法模式复杂的了。简单工厂模式的工厂类会随着产品类的个数增加需要增加很多方法(或代码),而工厂方法模式每个具体工厂类只完成单一任务,代码简洁
3>客户端编程难度
工厂方法模式虽然在工厂类结构中引入接口从而满足了OCP,但是在客户端编码中需要对工厂类进行实例化。而简单工厂模式的工厂类是个静态类,在客户端无需实例化,这无疑是个吸引人的优点。
4>管理上的难度
这是个关键的问题
首先先谈扩展,工厂方法模式完全满足OCP,即它有非常良好的的扩展性。那是否说明了简单工厂模式就没有扩展性了呢?答案是否定的,简单工厂模式同样具备良好的扩展性--扩展的时候仅需要修改少量代码(修改工厂类的代码)就可以满足扩展性的要求了,尽管没有完全满足OCP,但我们不需要太拘泥于设计理论。
然后从维护性的角度分析下,假如某个具体产品类需要进行一定的修改,很可能需要修改对应的工厂类。当同时需要修改多个产品类的时候,对工厂类的修改变得相当麻烦。反而简单工厂模式没有这些麻烦,当多个产品类需要修改时,简单工厂模式仍然仅仅需要修改唯一的工厂类
根据设计理论建议:工厂方法模式,但实际上,我们一般都用简单工厂模式
3)抽象工厂模式
--用来生产不同产品族的全部产品(对于新增加的产品,无能为力;支持增加产品族)
--抽象工厂模式是工厂方法模式的升级版本,在有多个业务品种、业务、分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式。
public interface Engine { void run(); void start(); } class LuxuryEngine implements Engine { @Override public void run() { System.out.println("转得快"); } @Override public void start() { System.out.println("启动快,可以自动启停"); } } class LowEngine implements Engine { @Override public void run() { System.out.println("转得慢"); } @Override public void start() { System.out.println("启动慢"); } }
public interface Seat { void massage(); } class LuxurySeat implements Seat { @Override public void massage() { System.out.println("可以自动按摩"); } } class LowSeat implements Seat { @Override public void massage() { System.out.println("不能按摩"); } }
public interface Tyre { void revolve(); } class LuxuryTyre implements Tyre { @Override public void revolve() { System.out.println("旋转不磨损"); } } class LowTyre implements Tyre { @Override public void revolve() { System.out.println("旋转磨损快"); } }
public interface CarFactory { Engine createEngine(); Seat createSeat(); Tyre createTyre(); }
public class LuxuryCarFactory implements CarFactory { @Override public Engine createEngine() { return new LuxuryEngine(); } @Override public Seat createSeat() { return new LuxurySeat(); } @Override public Tyre createTyre() { return new LuxuryTyre(); } }
public class LowCarFactory implements CarFactory { @Override public Engine createEngine() { return new LowEngine(); } @Override public Seat createSeat() { return new LowSeat(); } @Override public Tyre createTyre() { return new LowTyre(); } }
public class Client { public static void main(String[] args) { CarFactory factory = new LuxuryCarFactory(); Engine e = factory.createEngine(); Seat seat = factory.createSeat(); Tyre tyre = factory.createTyre(); e.run(); e.start(); CarFactory factory1 = new LowCarFactory(); Engine e1 = factory1.createEngine(); e1.run(); e1.start(); } }
3.建造者模式
本质:
--分离对象子组件的单独构造(由Buider来负责)和装配(由Director负责)。从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况下使用。
--由于实现构造和装配的解耦。不同的构造器,相同的装配,也可以做出不同的对象;相同的构造器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
public class AirShip { private OrbitalModule orbitalModule;//轨道舱 private Engine engine; //发动机 private EscapeTower escapeTower; //逃逸塔 public void launch() { System.out.println("发射"); } public OrbitalModule getOrbitalModule() { return orbitalModule; } public void setOrbitalModule(OrbitalModule orbitalModule) { this.orbitalModule = orbitalModule; } public Engine getEngine() { return engine; } public void setEngine(Engine engine) { this.engine = engine; } public EscapeTower getEscapeTower() { return escapeTower; } public void setEscapeTower(EscapeTower escapeTower) { this.escapeTower = escapeTower; } } class OrbitalModule{ private String name; public OrbitalModule(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } } class Engine{ private String name; public Engine(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } } class EscapeTower { private String name; public EscapeTower(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
public interface AirShipBuider { Engine buiderEngine(); OrbitalModule buiderOrbitalModule(); EscapeTower buiderEscapeTower(); }
public class SxtAirShipBuider implements AirShipBuider { @Override public Engine buiderEngine() { System.out.println("构建发动机"); return new Engine("尚学堂牌发动机"); } @Override public OrbitalModule buiderOrbitalModule() { System.out.println("构建轨道舱"); return new OrbitalModule("尚学堂牌轨道舱"); } @Override public EscapeTower buiderEscapeTower() { System.out.println("构建逃逸塔"); return new EscapeTower("尚学堂牌逃逸塔"); } }
public interface AirShipDirector { AirShip directorAirShip(); //组装飞船对象 }
public class SxtAirShipDirector implements AirShipDirector { private AirShipBuider buider; public SxtAirShipDirector(AirShipBuider buider) { this.buider = buider; } @Override public AirShip directorAirShip() { Engine e=buider.buiderEngine(); OrbitalModule o=buider.buiderOrbitalModule(); EscapeTower et = buider.buiderEscapeTower(); //装配成飞船 AirShip ship = new AirShip(); ship.setEngine(e); ship.setEscapeTower(et); ship.setOrbitalModule(o); return ship; } }
public class Client { public static void main(String[] args) { AirShipDirector director = new SxtAirShipDirector(new SxtAirShipBuider()); AirShip ship = director.directorAirShip(); System.out.println(ship.getEngine().getName()); System.out.println(ship.getEscapeTower().getName()); System.out.println(ship.getOrbitalModule().getName()); ship.launch(); } }
4.原型模式
--通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
--就是java中的克隆技术,以某个对象为原型,复制出新的对象。显然,新的对象具备原型对象的特点
--优势有:效率高(直接克隆,避免了重新执行构造过程步骤。)
--克隆类似于new,但是不同于new。new创建新的对象属性采用的是默认值。克隆出的对象的属性完全和原型对象相同。并且克隆出的新对象改变不会影响原型对象。然后再修改克隆对象的值。
1>原型模式的实现
--Cloneable接口和clone方法
--Prototype模式中实现起来最困难的地方就是内存复制操作,所幸在java中提供了clone()方法替我们做了绝大部分事情
2>浅复制
public class Sheep implements Cloneable { private String sname; private Date birthday; public String getSname() { return sname; } public void setSname(String sname) { this.sname = sname; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public Sheep(String sname, Date birthday) { this.sname = sname; this.birthday = birthday; } public Sheep() { } @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(1222232); Sheep s1 = new Sheep("少利", date); Sheep s2 = (Sheep) s1.clone(); System.out.println(s1); System.out.println(s1.getSname()); System.out.println(s1.getBirthday()); date.setTime(556344552); //修改出生日期,也会修改s2的出生日期 System.out.println(s1.getBirthday()); s2.setSname("多利"); System.out.println(s2); System.out.println(s2.getSname()); System.out.println(s2.getBirthday()); } }
3>深复制
public class Sheep2 implements Cloneable { private String sname; private Date birthday; public String getSname() { return sname; } public void setSname(String sname) { this.sname = sname; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public Sheep2(String sname, Date birthday) { this.sname = sname; this.birthday = birthday; } public Sheep2() { } @Override protected Object clone() throws CloneNotSupportedException { Object obj = super.clone();//直接调用object对象的clone()方法 //添加如下代码实现深复制 Sheep2 s = (Sheep2) obj; s.birthday = (Date) this.birthday.clone();//把属性也进行克隆 return obj; } }
/* * 原型模式(深复制) * */ public class Client2 { public static void main(String[] args) throws CloneNotSupportedException { Date date= new Date(1222232); Sheep2 s1 = new Sheep2("少利", date); Sheep2 s2 = (Sheep2) s1.clone(); //实现深复制,s2对象的birthday是一个新对象 System.out.println(s1); System.out.println(s1.getSname()); System.out.println(s1.getBirthday()); date.setTime(556344552); //此时不会修改s2的出生日期 System.out.println(s1.getBirthday()); s2.setSname("多利"); System.out.println(s2); System.out.println(s2.getSname()); System.out.println(s2.getBirthday()); } }
4>使用序列化和反序列化实现深复制
--在Sheep类记得实现Serializable接口
/* * 原型模式(深复制,使用序列化和反序列化的方式实现深复制) * */ public class Client3 { public static void main(String[] args) throws IOException, ClassNotFoundException { Date date= new Date(1222232); Sheep s1 = new Sheep("少利", date); System.out.println(s1); System.out.println(s1.getSname()); 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(556344552);//修改出生日期,也会修改s2的出生日期 System.out.println(s1.getBirthday()); s2.setSname("多利"); System.out.println(s2); System.out.println(s2.getSname()); System.out.println(s2.getBirthday()); } }
5>new方式创建对象和clone方式创建对象的效率差异
/* * 测试普通new方法创建对象和clone方式创建对象的效率差异 * 如果需要短时间创建大量对象,并且new的过程比较耗时。则可以考虑使用原型模式! * */ public class Client4 { public static void testNew(int size) { long start = System.currentTimeMillis(); for (int i = 0; i < size; i++) { Laptop t = new Laptop(); } long end = System.currentTimeMillis(); System.out.println("new方式创建耗时:"+(end-start)); } public static void testClone(int size) throws CloneNotSupportedException { long start = System.currentTimeMillis(); Laptop t = new Laptop(); for (int i = 0; i < size; i++) { Laptop temp = (Laptop) t.clone(); } long end = System.currentTimeMillis(); System.out.println("clone方式创建耗时:" + (end - start)); } public static void main(String[] args) throws CloneNotSupportedException { testNew(1000); testClone(1000); } } class Laptop implements Cloneable{ //笔记本电脑 public Laptop() { try { Thread.sleep(10); //模拟创建对象耗时的过程 } catch (InterruptedException e) { e.printStackTrace(); } } @Override protected Object clone() throws CloneNotSupportedException { Object obj = super.clone();//直接调用object对象的clone()方法 return obj; } }
开发中的应用场景:
--原型模式很少单独出现,一般是和工厂方法模式一起出现。通过clone的方法创建一个对象,然后由工厂方法提供给调用者
spring中bean的创建实际就是两种:单例模式和原型模式。(当然原型模式需要和工厂模式搭配起来)