目录
1、策略模式
主要解决if...else..的复杂和难以维护。一个公共接口,多个子类,一个策略使用类。
策略的选择可以结合枚举类使用,将枚举常量存在Map中或者存在枚举常量属性中。
2、代理模式
代理模式就是给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。
黄勇的《Proxy 那点事儿》写的超好,就不重复了,大概总结如下:
- 静态代理:代理类和委托类需要实现相同的接口,且需要为每一个委托类提供代理类
- jdk动态代理:利用反射机制生成一个实现委托类的接口的匿名类,所以需要委托类有接口。如果委托类有接口,默认采用jdk动态代理方式实现AOP,但也可以强制使用cglib。
public class DynamicProxy implements InvocationHandler {
private Object target;
public DynamicProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args);
after();
return result;
}
@SuppressWarnings("unchecked")
public <T> T getProxy() {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
...
}
//使用
public static void main(String[] args) {
DynamicProxy dynamicProxy = new DynamicProxy(new HelloImpl());
Hello helloProxy = dynamicProxy.getProxy();
helloProxy.say("Jack");
}
- cglib动态代理:加载代理对象类的class文件,通过修改字节码生成委托类的子类,所以不需要委托类有接口。如果委托类没有实现接口,必须采用cglib方式实现AOP。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。
public class CGLibProxy implements MethodInterceptor {
private static CGLibProxy instance = new CGLibProxy();
private CGLibProxy() {
}
public static CGLibProxy getInstance() {
return instance;
}
public <T> T getProxy(Class<T> cls) {
return (T) Enhancer.create(cls, this);
}
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
before();
Object result = proxy.invokeSuper(obj, args);
after();
return result;
}
...
}
//使用
public static void main(String[] args) {
HelloImpl helloImpl = CGLibProxy.getInstance().getProxy(HelloImpl.class);
helloImpl.say("Jack");
}
3、单例模式
注意点:私有化构造方法,只能在内部实例化,不能在外部实例化
-
饿汉式
缺点:类加载时就实例化
public class Singleton{
private Singleton(){} //私有化构造方法
private static Singleton instance = new Singleton();
public static Singleton getSingleton() {
return instance;
}
}
-
懒汉式(双重校验锁)
public class Singleton{
private Singleton(){} //私有化构造方法
private volatile static Singleton instance;
public static Singleton getSingleton() {
if(instance == null) { //第一次校验
synchronized(Singleton.class) { //加锁
if(instance == null) { //第二次校验
instance = new Singleton();
}
}
}
return instance;
}
}
为什么使用volatile:
volatile有两个作用,一个是保证变量在多个线程可见性(线程在使用线程栈内存的变量副本时必须从主存刷新),二是禁止指令重排,重排序指编译器和处理器为了优化程序性能而对指令序列重新排序的一种手段。此处便是为了禁止指令重排,因为在创建对象时,分配内存、初始化、引用指向分配的内存这三步中的后两步可能会被重排,若线程1分配内存然后引用指向分配的内存但还未初始化,instance已经是非null了但并未初始化,线程2获得cpu时间片会直接返回一个未完成的实例从而产生错误。
memory = allocate(); // 1:分配对象的内存空间
ctorInstance(memory); // 2:初始化对象
instance = memory; // 3:设置instance指向刚分配的内存地址
为什么要双重校验:
首先第二次校验是必须的,因为若没有此次校验,当两个线程同时执行到加锁语句,一个获得锁进入同步代码块产生实例对象,释放锁后第二个线程获得锁进入同步代码块,此时若没有第二次校验,则第二个线程仍然会产生实例化对象。第一次校验是为了性能优化,同步的代价是很高的,为了避免已经生成对象后其他的线程还继续获得锁释放锁,所以加第一次校验,其他线程进入getSingleton方法后判断实例不为null就直接返回。
-
静态内部类模式
public class Singleton{
private Singleton(){} //私有化构造方法
private static class Inner{
private static Singleton instance = new Singleton();
}
public static Singleton getSingleton() {
return Inner.instance;
}
}
内部类的加载不依赖外部类,只有当调用getSingleton()方法时Inner才加载,实现了懒加载。不用担心instance创建过程线程安全问题,因为虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。如果在一个类的<clinit>()方法中有耗时很长的操作,就可能造成多个进程阻塞(需要注意的是,其他线程虽然会被阻塞,但如果执行<clinit>()方法后,其他线程唤醒之后不会再次进入<clinit>()方法。同一个加载器下,一个类型只会初始化一次。
4、适配器模式
将一个类的接口转换成客户希望的另外一个接口,分为类的适配器模式(继承)和对象的适配器模式(组合)。其实日常中我们经常使用对象的适配器模式,比如你定义好了一个接口,但接口中的方法已经在某个地方实现了,想拿过来直接用,则注入实现类的对象。
- 类的适配器:
//首先有一个已存在的将被适配的类
public class Adaptee {
public void adapteeRequest() {
System.out.println("被适配者的方法");
}
}
//定义一个目标接口
public interface Target {
void request();
}
//适配方法
public class Adapter extends Adaptee implements Target{
@Override
public void request() {
//...一些操作...
super.adapteeRequest();
//...一些操作...
}
}
- 对象的适配器模式:
public class Adapter implements Target{
// 适配者是对象适配器的一个属性
private Adaptee adaptee = new Adaptee();
@Override
public void request() {
//...
adaptee.adapteeRequest();
//...
}
}
5、装饰模式
6、工厂模式
7、观察者模式
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。需要注意: 1、JAVA 中已经有了对观察者模式的支持类。 2、避免循环引用。 3、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。
通俗理解为:把要通知的对象加入到观察对象的集合中,当观察对象发生变化时遍历集合的通知对象,调用其通知方法。
//通知对象接口
public abstract class Observer {
protected Subject subject;
public abstract void update();
}
//通知对象1
public class BinaryObserver extends Observer{
public BinaryObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println( "Binary String: "
+ Integer.toBinaryString( subject.getState() ) );
}
}
//通知对象2
public class OctalObserver extends Observer{
public OctalObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println( "Octal String: "
+ Integer.toOctalString( subject.getState() ) );
}
}
//观察对象
public class Subject {
private List<Observer> observers = new ArrayList<Observer>();
private int state;
public int getState() {
return state;
}
public void attach(Observer observer){
observers.add(observer);
}
//当调用此set方法时,会发通知
public void setState(int state) {
this.state = state;
notifyAllObservers();
}
public void notifyAllObservers(){
for (Observer observer : observers) {
observer.update();
}
}
}
8、组合模式
主要解决:希望用户忽略组合对象与单个对象的不同,无需关心处理的是单个对象还是组合的对象容器。
优点: 1、高层模块调用简单。 2、节点自由增加。
角色:
- 抽象构件:可以是接口或者抽象类,是叶子构件和容器构件的接口,包含子类行为的声明和实现。
- Leaf(叶子构件):叶子节点没有子节点,实现了抽象构件定义的行为。
- Composite(容器构件):既可以存储子节点也可以存储容器节点,就像文件夹,既可以存文件,也可以存文件夹。
主要实现:树枝和叶子实现统一接口,树枝内部组合该接口。
在代码具体实现上,有两种不同的方式:
- 透明组合模式把所有公共方法都定义在 抽象构件中,这样做的好处是客户端无需分辨是叶子节点(Leaf)和树枝节点(Composite),它们具备完全一致的接口;缺点是叶子节点(Leaf)会继承得到一些它所不需要(管理子类操作的方法)的方法
- 把容器构件特有的行为(管理子类增加,删除等)放到自身(Composite)中。这样做的好处是接口定义职责清晰,符合设计模式 单一职责原则 和 接口隔离原则;缺点是客户需要区分树枝节点(Composite)和叶子节点(Leaf),这样才能正确处理各个层次的操作,客户端无法依赖抽象(Component),违背了设计模式 依赖倒置原则。
应用实例:
1、java.awt,第一代的Java GUI组件,我们可以在Frame容器中添加不同的构件,比如 Button、Label、TextField,还可以添加 Panel 面板,Panel 容器中又可以添加了Button、Label、TextField 等构件,为什么容器 Frame 和 Panel 可以添加类型不同的构件和容器呢? Frame 和 Panel继承了容器父类 Container,内部定义了一个集合用于存储 Component 对象,而容器组件 Container 和 基本组件如 Button、Label、TextField 等都是 Component 的子类,所以可以很清楚的看到这里应用了组合模式。
2、MyBatis 的动态SQL通过 if, choose, when, otherwise, trim, where, set, foreach 标签可以组合成非常灵活的SQL语句。Mybatis在处理动态SQL节点时,应用到了组合设计模式,Mybatis会将映射配置文件中定义的动态SQL节点、文本节点等解析成对应的 SqlNode 实现,并形成树形结构。抽象构件为 SqlNode 接口,MixedSqlNode 扮演了容器构件角色,维护了一个 List<SqlNode> 类型的列表,用于存储 SqlNode 对象,apply 方法通过 for循环 遍历 contents 并调用其中对象的 apply 方法,IfSqlNode、WhereSqlNode 等是叶子构件角色。
9、模板模式
子类对同一个方法有不同的实现,但该方法的骨架或者基准却是可以像模板一样定义好的。
public abstract class Game {
abstract void initialize();
abstract void startPlay();
abstract void endPlay();
//模板
public final void play(){
//初始化游戏
initialize();
//开始游戏
startPlay();
//结束游戏
endPlay();
}
}
10、不变模式(不属于设计模式)
多线程对同一个对象进行读写操作时,需要耗费系统性能使用同步操作来保证数据的正确性。为了尽可能去除同步操作,在某些场景下,可以使用一种不可改变的对象。JDK中,不变模式的应用非常广泛,java.lang.String类和所有的元数据类包装类,都是使用不变模式实现的。
实现非常简单,只需要满足几点:
- 去除setter方法和所有修改自身属性的方法
- 属性设置为private final
- 类使用final
- 有一个可以创建完整对象的构造函数
//1.类名由final修饰
public final class Product {
//2.属性由private final修饰
private final String no;
private final String name;
private final double price;
//3.没有设置setter方法
//4.可以创建完整对象的构造函数
public Product(String no, String name, double price) {
this.no = no;
this.name = name;
this.price = price;
}
}