将设计者的思维融入大家的学习和工作中,更高层次的思考!
创建型模式 :
单例模式,工厂模式,抽象工厂模式,建造者模式,原型模式;
结构型模式 :
适配器模式,桥接模式,装饰模式,组合模式,外观模式,享元模式,代理模式;
行为型模式 :
模板方法模式,命令模式,迭代器模式,观察者模式,中介者模式,备忘录模式,解释器模式,状态模式,策略模式,职责链模式,访问者模式;
静态内部类实现方式(也是一种赖加载方式)
public class SingletonDemo04 {
private static class SingletonClassInstance {
private static final SingletonDemo04 instance = new SingletonDemo04();
}
public static SingletonDemo04 getInstance() {
return SingletonClassInstance.instance;
}
private SingletonDemo04() {
}
}
要点 :
1: 外部类没有static属性, 则不会像饿汉式哪有立即加载对象.
2: 只有真正调用getInstance(), 才会加载静态内部类. 加载类时是线程安全的. instance是static final类型, 保证了内存中只有这样一个实例存在, 而且只能被
赋值一次, 从而保证了线程安全性.
兼备了并发高效调用和延迟加载的优势.
用枚举实现单例模式(没有延迟加载)
public enum SinglettomDemo5 {
// 这个枚举元素, 本身就是单例对象
INSTANCE;
// 添加自己需要的操作
public void singletonOperation() {
}
}
常见的五种单例模式实现方式 :
主要 :
1 : 饿汉式(线程安全, 调用效率高. 但是, 不能延时加载).
2 : 懒汉式(线程安全, 调用效率不高. 但是, 可以延时加载.)
其他 :
1 : 双重检测锁式(由于JVM底层内部模型原因, 偶尔会出现问题. 不建议使用)
2 : 静态内部类式(线程安全, 调用效率高. 但是, 可以延时加载)
3 : 枚举式(线程安全, 调用效率高, 不能延时加载. 并且可以天然的防止反射和反序列化漏洞!)
如何选用?
单例对象 : 占用 资源少, 不需要延时加载 :
枚举式 好于 饿汉式
单例对象 占用 资源大, 需要延时加载 :
静态内部类式 好于饿汉式
package com.example.demo.test;
import java.io.ObjectStreamException;
import java.io.Serializable;
/**
* 测试懒汉式单例模式(如何防止反射和反序列化漏洞)
*/
public class SingletonDemo6 implements Serializable{
// 类初始化时,不初始化这个对象(延时加载, 真正用的时候再创建)
private static SingletonDemo6 instance;
// 私有化构造器
private SingletonDemo6() {
}
// 方法同步,调用效率低!
public static synchronized SingletonDemo6 getInstance() {
if (instance == null) {
return instance = new SingletonDemo6();
}
return instance;
}
// 反序列化时, 如果定义了readResolve()则直接返回此方法指定的对象. 而不需要单独再创建新对象!
private Object readResolve() throws ObjectStreamException {
return instance;
}
}
package com.example.demo.test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Client2 {
public static void main(String[] args) throws Exception {
SingletonDemo6 singletonDemo6 = SingletonDemo6.getInstance();
SingletonDemo6 singletonDemo61 = SingletonDemo6.getInstance();
System.out.println(singletonDemo6);
System.out.println(singletonDemo61);
// 通过反射的方式直接调用私有构造器
/*Class<SingletonDemo6> clazz = SingletonDemo6.class;
Constructor<SingletonDemo6> c = clazz.getDeclaredConstructor(null);
c.setAccessible(true);
SingletonDemo6 s3 = c.newInstance();
SingletonDemo6 s4 = c.newInstance();
System.out.println(s3);
System.out.println(s4);*/
// 通过反序列化的方式构造多个对象
FileOutputStream fos = new FileOutputStream("d:/a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(singletonDemo6);
oos.close();
fos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/a.txt"));
SingletonDemo6 s3 = (SingletonDemo6) ois.readObject();
System.out.println(s3);
}
}
package com.example.demo.test;
import java.util.concurrent.CountDownLatch;
public class Client3 {
public static void main(String[] srgs) throws Exception {
long start = System.currentTimeMillis();
int count = 10;
final CountDownLatch countDownLatch = new CountDownLatch(count);
for (int i = 0; i < count; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000000; i++) {
Object o = SingletonDemo6.getInstance();
}
countDownLatch.countDown();
}
}).start();
}
// main线程阻塞, 指定计数器为0,才会继续往下执行!
countDownLatch.await();
long end = System.currentTimeMillis();
System.out.println("总耗时 : " + (end - start));
}
}
CountDownLatch是一个同步工具,它主要用线程执行之间的协作.CountDownLatch的作用和Thread.join()方法类似.让一些线程阻塞直到另一些线程完成一系列操作后才被唤醒.
在之间创建线程的年代(java5.0之前), 我们可以使用Thread.join().在线程池出现后,因为线程池中的线程不能直接被引用,所以就必须使用CountDownLatch了.
CountDownLatch主要有两个方法, 当一个或多个线程调用await方法时,这些线程会阻塞.其他线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞),
当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行.
实现原理 :
1: 计数器的值由构造函数传入,并用它初始化AQS的state值.当线程调用await方法时会检查state的值是否为0,如果是就直接返回(既不会阻塞); 如果不是, 将表示该节点
的线程入列, 然后将自身阻塞.当其它线程调用countDown方法会将计数器减1,然后判断计数器的值是否为0,当它为0时,会唤醒队列中的第一个节点,由于CountDownLatch使用了
AQS的共享模式,所以第一个节点被唤醒后又会唤醒第二个节点,以此类推,使得所有因await方法阻塞的线程都能被唤醒而继续执行.
从源码和实现原理中可以看出一个CountDownLatch对象,只能使用一次,不能重复使用.
await方法源码 :
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg) throws InterruptedException{
if (Thread.interrupted()) {
throw new InterruptedException();
}
if (tryAcquireShared(arg) < 0) {
doAcquireSharedInterruptibly(arg);
}
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
doAcquireSharedInterruptibly 主要实现线程的入列与阻塞
countDown方法 :
public void countDown() {
sync.releaseShared();
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0) {
return false;
}
int nextc = c-1;
if (compareAndSetState(c, nextc)) {
return nextc == 0;
}
}
}
doReleaseShared主要实现唤醒第一个节点, 第一个节点有会唤醒第二个节点,
使用示例 :
public class CountDownLatchDemo {
private CountDownLatch cd1 = new CountDownLatch(2);
private Random rnd = new Random();
class FirstTask implements Runnable {
private String id;
public FirstTask(String id) {
this.id = id;
}
@Override
public void run() {
System.out.println("Thread" + id + "is start");
try {
Thread.sleep(rnd.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread" + id + "is over");
cd1.countDown();
}
}
class SecondTask implements Runnable {
private String id;
public SecondTask(String id) {
this.id = id;
}
@Override
public void run() {
try {
cd1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------Thread" + id + " is start");
try {
Thread.sleep(rnd.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-------Thread" + id + " is over")
}
}
public static void main(String[] args) {
ExecutorService es = Executors.newCachedThreadPool();
CountDownLatchDemo cold = new CountDownLatchDemo();
es.submit(cdld.new SecondTask("c"));
es.submit(cdld.new SecondTask("d"));
es.submit(cdld.new FirstTask("a"));
es.submit(cdld.new FirstTask("b"));
es.shutdown();
}
}
工厂模式 :
工厂模式 : 实现了创建者和调用者的分离.
详细分类 :
简单工厂模式,工厂方法模式,抽象工厂模式.
面向对象设计的基本原则 :
OCP(开闭原则,Open-Closed Principle) : 一个软件的实体应当对扩展开放,对修改关闭.
DIP(依赖倒转原则, Dependence Inversion Principle) : 要针对接口编程,不要针对实现编程.实现尽量依赖抽象,不依赖具体实现.
LoD(迪米特法则,Law of Demeter) : 只与你直接的朋友通信,而避免和陌生人通信.又叫最少知识原则,一个软件的实体应尽可能少的与其他实体 发送相互作用 .
单一职责原则 : 每一个类应该专注于做一件事情.
里氏替换原则 : 超类存在的地方,子类是可以替换的.
接口隔离原则 : 应当为客户端提供尽可能小的单独的接口,而不是提供大的总得接口.
组合/聚合复用原则 : 尽量使用合成/聚合达到复用,尽量少用继承. 原则 : 一个类中有另一个类的对象.
工厂模式的核心本质 :
实例化对象,用工厂方法代替new操作.
将选择实现类,创建对象统一管理和控制.从而将调用者跟我们的实现类解耦.
工厂模式 :
简单工厂模式 : 用来生产同一等级结构中的任意产品.(对于增加新的产品,需要修改已有代码)
工厂方法模式 : 用来生产同一等级结构中的固定产品.(支持增加任意产品)
抽象工厂模式 : 用来生产不同产品族的全部产品.(对于增加新的产品,无能为力;支持增加产品族)
简单工厂 :
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 CarFactory {
public static Car createAudi() {
return new Audi();
}
public static Car createBeans() {
return new Bens();
}
}
工厂方法模式要点 :
为力避免简单工厂模式的缺点,不完全满足OCP.
工厂方法模式和简单工厂模式最大的不同在于,简单工厂模式只有一个(对于一个项目或者一个独立模块而言)工厂类,而工厂方法模式有一组实现了相同接口的工厂类.
抽象工厂模式 :
用来生产不同产品族的全部产品.(对于增加新的产品,无能为力,支持增加产品族)
抽象工厂模式是工厂方法模式的升级版本,在有多个业务品种,业务分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式.
工厂模式要点 :
简单工厂模式(静态工厂模式) : 虽然某种程度不符合设计原则,但实际使用最多.
工厂方法模式 : 不修改已有类的前提下,通过增加新的工厂类实现扩展.
抽象工厂模式 : 不可以增加产品,可以增加产品族.
应用场景 :
JDK中Calendar的getInstance方法;
JDBC中Connection对象的获取;
Hibernate中SessionFactory创建Session;
spring中IOC容器创建管理bean对象;
XML解析时的DocumentBuilderFactory创建解析器对象;
反射中Class对象的newInstance();
建造者模式 :
在实际开发中,我们所需要的对象构建时,也非常复杂,有很多步骤需要处理时.
建造模式的本质 :
分离了对象子组件的单独构造(由Builder来负责)和装配(由Director负责).从而可以构造出复杂的对象.这个模式适用于 : 某个对象的构建过程复杂的情况下使用.
由于实现了构建和装配的解耦.不同的构建器,相同的装配,也可以做出不同的对象.也就是实现了构建算法,装配算法的解耦,实现了更好的复用.
开发中应用场景 :
例如StringBuilder : 利用了构建者模式;还有XML解析中,JDOM库中的类 : DomBuilder,SaxBuilder也是利用了构建者模式;SQL中的PreparedStatement
原型模式prototype :
通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式.
就是java中的克隆技术,以某个对象为原型,复制出新的对象.显然,新的对象具备原型对象的特定;
优势有 : 效率高(直接克隆,避免了重新执行构造过程步骤)
克隆类似于new,但是不同于new.new创建新的对象属性采用的是默认值.克隆出的对象的属性值完全和原型对象相同.并且克隆出的新对象改变不会影响原型对象.然后,
再修改克隆对象的值.
原型模式实现 :
Cloneable接口和clone方法
Prototype模式中实现起来最困难的地方就是内存复制操作,所幸在Java中提供了clone()方法替我们做了绝大部分事情.
注意用词 : 克隆和拷贝一回事.
例子 :
public class Sheep implements Cloneable {
private String sname;
private Date birthday;
public Sheep(String sname, Date date) {
this.sname = sname;
this.birthday = date;
}
@Override
protected Object clone() throws CloneNotSupportedException {
// 直接调用object对象的clone方法
Object clone = super.clone();
//添加如下代码实现深复制(deep Clone)
Sheep s = (Sheep)clone;
// 把属性也进行克隆(浅克隆对于对象的克隆只是克隆了引用地址,当引用地址指向的原地址中值改变了,克隆以后的值也改变了.而深克隆是专门把原有的再复制出来一份)
s.birthday = (Date) this.birthday.clone();
return clone;
}
利用序列化和反序列化技术实现深克隆 :
开发中的应用场景 : 原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者.
spring中bean的创建实际就是两种 : 单例模式和原型模式.(当然,原型模式需要和工厂模式搭配起来使用)
例子 :
Date date = new Date();
Sheep s1 = new Sheep("少理",date);
System.out.println(s1);
System.out.println(s1.toString());
// 使用序列化和反序列化实现深复制
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();
System.out.println("修改克隆好的对象属性值");
s1.setBirthday(new Date(18239139371319l));
System.out.println("----------------------");
// Sheep clone = (Sheep)s1.clone();
System.out.println(s2);
System.out.println(s2.toString());
注意 : 如果需要短时间创建大量对象,并且new的过程比较耗时.则可以考虑使用原型模式.
总结 :
创建型模式 : 都是用来帮助我们创建对象的.
单例模式 : 保证一个类只有一个实例,并且提供一个访问该实例的全局访问点.
工厂模式 :
简单工厂模式 : 用来生产同一等级结构中的任意产品.(对于增加新的产品,需要修改已有代码)
工厂方法模式 : 用来生产同一等级结构中的固定产品.(支持增加任意产品)
抽象工厂模式 : 用来生产不同产品族的全部产品.(对于增加新的产品,无能为力;支持增加产品族)
建造者模式 :
分离了对象子组件的单独构造(由Builder来负责)和装配(由Director负责).从而可以构造出复杂的对象.
原型模式 :
通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式.
结构型模式 :
核心作用 : 是从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题.
分类 :
适配器模式,代理模式,桥接模式,装饰模式,组合模式,外观模式,享元模式.
什么是适配器模式?
将一个类的接口转换成客户希望的另外一个接口.Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作.
模式中的角色 :
目标接口(Target) : 客户所期待的接口.目标可以是具体的或抽象的类,也可以是接口.
需要适配的类(Adaptee) : 需要适配的类或适配者类.
适配器(Adapter) : 通过包装一个需要适配的对象,把原接口转换成目标接口.
工作中的场景 :
经常用来做旧系统改造和升级;
如果我们的系统开发之后再也不需要维护,那么很多模式都是没必要的,但是不幸的是,事实却是维护一个系统的代价往往是开发一个系统的数倍.
我们常见的场景 :
java.io.InputStreamReader(InputStream)
java.io.OutputStreamWriter(OutputStream)
例子 :
package com.example.demo.adapter;
/**
* 被适配的类
* (相当于例子中的,PS/2键盘)
*/
public class Adaptee {
public void request() {
System.out.println("可以完成客户请求的功能!");
}
}
package com.example.demo.adapter;
/**
* 适配器(相当于usb和ps/2的转接器)(类适配器方式)
*/
public class Adapter extends Adaptee implements Target {
@Override
public void handleReq() {
super.request();
}
}
package com.example.demo.adapter;
/**
* 适配器(对象适配器方式,使用了组合的方式跟被适配对象整合)
* (相当于usb和ps/2的转接器)
*/
public class Adapter2 implements Target {
private Adaptee adaptee;
@Override
public void handleReq() {
adaptee.request();
}
public Adapter2(Adaptee adaptee) {
this.adaptee = adaptee;
}
}
package com.example.demo.adapter;
public interface Target {
void handleReq();
}
package com.example.demo.adapter;
/**
* 客户的类
* (相当于例子中的笔记本,只有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 Adapter();
c.test1(t);*/
Client c = new Client();
Adaptee adaptee = new Adaptee();
Target target = new Adapter2(adaptee);
c.test1(target);
}
}
代理模式(Proxy pattern) :
核心作用 :
通过代理,控制对对象的访问!
可以详细控制访问某个(某类)对象的方法,在调用这个方法前做前置处理,调用这个方法后做后置处理.(既 : AOP的微观实现!)
AOP(面向切面编程)的核心实现机制!
核心角色 :
抽象角色 : 定义代理角色和真实角色的公共对外方法;
真实角色 :
实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用.关注真正的业务逻辑!
代理角色 :
实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作.
将统一的流程控制放到代理角色中处理!
应用场景 :
安全代理 : 屏蔽对真实角色的直接访问.
远程代理 : 通过代理类处理远程方法调用(RMI)
延迟加载 : 先加载轻量级的代理对象,真正需要再加载真实对象.
(比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100MB,在打卡文件时不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要
查看图片时,用proxy来进行大图片的打卡.)
分类 :
静态代理(静态定义代理类)
动态代理(动态生成代类)
JDK自带的动态代理;
javaassist字节码操作库实现
CGLIB
ASM(底层使用指令,可维护性较差)
动态代理的优点 :
动态代理相比于静态代理的优点 :
抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,我们可以更加灵活和统一的处理众多的方法.
JDK自带的实现 :
java.lang.reflect.Proxy : 作用 : 动态生成代理类和对象.
java.lang.reflect.InvocationHandler(处理器接口) :
可以通过invoke方法实现对真实角色的代理访问.
每次通过Proxy生成代理类对象对象时都要指定对应的处理器对象.
例子 :
Star realStar = new RealStar();
StarHandler handler = new StarHandler(realStar);
Star proxy = (Star)Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Star.class},handler);
proxy.sing();
开发框架中应用场景 :
struts2中拦截器的实现;
数据库连接处关闭处理;
Hibernate中延迟加载的实现;
mybatis中实现拦截器插件;
AspectJ的实现;
spring中AOP的实现 : 日志拦截,声明式事务处理;
web service;
RMI远程方法调用;
桥接模式(bridge) :
场景 : 商城系统中常见的商品分类,以电脑为类,如何良好的处理商品分类销售的问题.
用来处理多层继承结构;
问题 :
扩展性问题(类个数膨胀问题) :
如果要增加一个新的电脑类型智能手机,则要增加各个品牌下面的类.
如果要增加一个新的品牌,也要增加各种电脑类型的类.
违反单一职责原则 :
一个类 : 联想笔记本,有两个引起这个类变化的原因.
桥接模式核心要点 :
处理多层继承结构,处理多维度变化的场景,将各个维度设计成独立的继承结构,使各个维度可以独立的扩展在抽象层建立关联.
桥接模式总结 :
桥接模式可以取代多层继承的方案.多层继承违背了单一职责原则,复用性较差,类的个数也非常多.桥接模式可以极大的减少子类的个数,从而降低管理和维护的成本.
桥接模式极大的提高了系统可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有的系统,符合开闭原则.
就像一个桥,将两个变化维度连接起来,各个维度都可以独立的变化.故称之为 : 桥模式.(只要具备多个编号维度的时候,就可以使用桥接模式)
桥接模式实际开发中应用场景 :
JDBC驱动程序;
AWT中的Peer架构;
银行日志管理;
格式分类 : 操作日志,交易日志,异常日志;
距离分类 : 本地记录日志,异地记录日志;
人力资源系统中的奖金计算模块 :
奖金分类 : 个人奖金,团体奖金,激励奖金.
部门分类 : 人事部门,销售部门,研发部门.
OA系统中的消息处理 :
业务类型 : 普通消息,加急消息,特急消息;
发送消息方式 : 系统内消息,手机短信,邮件;
组合模式(composite)
使用组合模式的场景 :
把部分和整体的关系用树形结构来表示,从而使客户端可以使用统一的方式处理部分对象和整体对象.
组合模式核心 :
抽象构件(Component)角色 : 定义了叶子和容器构件的共同点;
叶子(Leaf)构件角色 : 无子节点;
容器(Composite)构件角色 : 有容器特征,可以包含子节点.
组合模式工作流程分析 :
组合模式为处理树形结构提供了完美的解决方案,描述了如何将容器和叶子进行递归组合,使得用户在使用时可以一致性的对待容器和叶子.
当容器对象的指定方法被调用时,将遍历整个树形结构,寻找也包含这个方法的成员,并调用执行.其中,使用了递归调用的机制对整个结构进行处理.
使用组合模式,模拟杀毒软件架构设计.
开发中的应用场景 :
操作系统的资源管理器;
GUI中的容器层次图;
XML文件解析;
OA系统中,组织结构的处理;
Junit单元测试框架 : 底层设计就是典型的组合模式,TestCase(叶子),TestUnit(容器),Test接口(抽象)
例子 :
package com.example.demo.composite;
import java.util.ArrayList;
import java.util.List;
// 抽象构建
public interface AbstractFile {
// 杀毒
void killVirus();
}
class ImageFile implements AbstractFile {
private String name;
public ImageFile(String name) {
this.name = name;
}
@Override
public void killVirus() {
System.out.println("--图像文件 : " + name + ",进行查杀!");
}
}
class TextFile implements AbstractFile {
private String name;
public TextFile(String name) {
this.name = name;
}
@Override
public void killVirus() {
System.out.println("---文本文件 : " + name + ",进行查杀!");
}
}
class VideoFile implements AbstractFile {
private String name;
public VideoFile(String name) {
this.name = name;
}
@Override
public void killVirus() {
System.out.println("---视频文件 : " + name + ",进行查杀!");
}
}
class Folder implements AbstractFile {
private String name;
// 定义容器,用来存放本容器构建下的子节点
private List<AbstractFile> list = new ArrayList<AbstractFile>();
public Folder(String name) {
this.name = name;
}
public void add(AbstractFile file) {
list.add(file);
}
public void remove(AbstractFile file) {
list.remove(file);
}
public AbstractFile getChild(int index) {
return list.get(index);
}
@Override
public void killVirus() {
System.out.println("---文件夹 : " + name + ",进行查杀!");
for (AbstractFile file : list) {
file.killVirus();
}
}
}
package com.example.demo.composite;
public class Clinet {
public static void main(String[] args) {
AbstractFile f2, f3, f4, f5, f6;
Folder f1 = new Folder("我的收藏");
f2 = new ImageFile("老高的大头像.jpg");
f3 = new TextFile("Hello.txt");
f1.add(f2);
f1.add(f3);
Folder f11 = new Folder("电影");
f4 = new VideoFile("笑傲江湖.avi");
f5 = new VideoFile("神雕侠侣.avi");
f11.add(f4);
f11.add(f5);
f1.add(f11);
//f2.killVirus();
f1.killVirus();
}
}
装饰模式(decorator)
职责 :
动态的为一个对象增加新的功能.
装饰模式是一种用于代替继承的技术,无须通过继承增加子类就能扩展对象的新功能.使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀.
实现细节 :
Component抽象构建角色 : 真实对象和装饰对象有相同的接口.这样,客户端对象就能够以与真实对象相同的方式同装饰对象交互.
ConcreteComponent具体构建角色(真实对象) : io流中的FileInputStream,FileOutputStream
Decorator装饰角色 : 持有一个抽象构建的引用.装饰对象接受所有客户端的请求,并把这些请求转发给真实的对象.这样,就能在真实对象调用前后增加新的功能.
ConcreteDecorator具体装饰角色 : 负责给构建对象增加新的责任.
开发中使用的场景 :
IO中输入流和输出流的设计;
Swing包中图形界面构件功能;
Servlet API中提供了一个request对象的Decorator设计模式的默认实现类HttpServletRequestWrapper,HttpServletRequestWrapper类,增强了request对象的功能.
Struts2中,request,response,session对象的处理.
IO流实现细节 :
Component抽象构件角色 : io流中的InputStream,OutputStream,Reader,Writer
ConcreteComponent具体构件角色 : io流中的FileInputStream,FileOutputStream
Decorator装饰角色 : 持有一个抽象构件的引用 : io流中的FilterInputStream,FilterOutputStream
COncreteDecorato具体装饰角色 : 负责给构建对象增加新的责任.Io流中的BufferedOutputStream,BufferedInputStream等.
总结 :
装饰模式(Decorator)也叫包装器模式(Wrapper)
装饰模式降低系统的耦合度,可以动态的增加或删除对象的职责,并使得需要装饰的具体构建类和具体装饰类可以独立变化,以便增加新的具体构建类和具体装饰类.
优点 :
扩展对象功能,比继承灵活,不会导致类个数急剧增加.
可以对一个对象进行多次装饰,创造出不同行为的组合,得到功能更加强大的对象.
具体构建类和具体装饰类可以独立变化,用户可以根据需要自己增加新的具体构建子类和具体装饰子类.
缺点 :
产生很多小对象.大量小对象占据内存,一定程度上影响性能.
装饰模式易于出错,调试排查比较麻烦.
装饰模式和桥接模式的区别 :
两个模式都是为了解决过多子类对象问题.但他们诱因不一样.桥接模式是对象自身现有机制沿着多个维度变化,是既有部分不稳定.装饰模式是为了增加新的功能.
例子 :
package com.example.demo.decorator;
/**
* 抽象构建
*/
public interface ICar {
void move();
}
// ConcreteComponent 具体构件角色(真实对象)
class Car implements ICar {
@Override
public void move() {
System.out.println("陆地上跑!");
}
}
// Decorator装饰角色
class SuperCar implements ICar {
protected ICar car;
public SuperCar(ICar car) {
this.car = car;
}
@Override
public void move() {
car.move();
}
}
// ConcreteDecorator具体装饰角色
class FlyCar extends SuperCar {
public FlyCar(ICar car) {
super(car);
}
public void fly() {
System.out.println("天上飞!");
}
@Override
public void move() {
super.move();
fly();
}
}
// ConcreteDecorator具体装饰角色
class WaterCar extends SuperCar {
public WaterCar(ICar car) {
super(car);
}
public void swim() {
System.out.println("水上飞!");
}
@Override
public void move() {
super.move();
swim();
}
}
// ConcreteDecorator具体装饰角色
class AICar extends SuperCar {
public AICar(ICar car) {
super(car);
}
public void autoMove() {
System.out.println("自动跑的机器人!");
}
@Override
public void move() {
super.move();
autoMove();
}
}
package com.example.demo.decorator;
public class Client {
public static void main(String[] args) {
Car car = new Car();
car.move();
System.out.println("增加新的功能,飞行 -----");
FlyCar car1 = new FlyCar(car);
car1.move();
System.out.println("增加新的功能,水里游-----");
WaterCar waterCar = new WaterCar(car);
waterCar.move();
System.out.println("增加两个新的功能,飞行,水里游-------");
WaterCar waterCar1 = new WaterCar(new FlyCar(car));
waterCar1.move();
}
}