本文根据狂神说和黑马课程总结,大家可以去看原版视频讲解。
设计模式就是解决特定问题的一系列套路,不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性以及安全性的解决方案。
创建型模式
描述如何创建对象
- 单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
结构型模式
描述将对象或类按某种布局组成更大的结构
- 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
行为型模式
描述类或者对象之间如何相互协作,完成单个对象无法完成的内容
- 模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式
OOP七大原则
- 开闭原则:对扩展开放,对修改关闭
- 里氏替换原则:继续必须确保父类所拥有的性质在子类中仍然成立(继承父类时尽量不要改变父类原有的功能)
- 依赖倒置原则:要面向接口编程,不要面向实现编程。
- 单一职责原则:控制类的粒度大小、将对象解耦、提高其内聚性。一个方法尽可能只做一件事
- 接口隔离原则:要为各个类建立它们需要的专用接口
- 迪米特法则:与你的直接朋友交谈,不跟陌生人说话,A和B沟通,B和C沟通,但不能A和C沟通
- 合成复用原则:尽量先使用组合或者聚合等关联关系来实现(组合已有对象生成新对象的功能),其次才考虑使用继承关系来实现。
单例模式
单例模式是Java中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建,这个类提供了一种访问其唯一的对象方式,可以直接访问,不需要实例化该类的对象。
饿汉式
类加载就会导致该实例对象被创建
- 静态变量方式实现
// 饿汉式:静态成员的方式
public class Singleton {
// 私有构造方法
private Singleton(){
}
// 在本类中创建该类对象
private static Singleton instance = new Singleton();
// 提供一个公共的访问方式,让外界获取该对象
public static Singleton getInstance(){
return instance;
}
}
// 客户测试类:测试是否为同一对象
public class Client {
public static void main(String[] args) {
// 创建Singleton类的对象
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2);
}
}
- 静态代码块方式实现
public class Singleton {
// 私有构造方法
private Singleton(){
}
// 声明Singleton类的变量
private static Singleton instance; // 此时为null
// 在静态代码块中进行赋值
static {
instance = new Singleton();
}
// 对外获取该类对象的方法
public static Singleton getInstance(){
return instance;
}
}
懒汉式
类加载不会导致该实例对象被创建,而是首次使用该对象时才会创建
- 方式1(线程不安全)
public class Singleton {
private Singleton(){
}
// 声明Singleton类型的变量
private static Singleton instance;
// 对外提供访问方式
public static Singleton getInstance(){
// 判断instance是否为null,如果为null,则还未创建对象
// 如果创建,直接返回instance
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
public class Client{
public static void main(String[] args){
Singleton instance1 = new Singleton();
Singleton instance2 = new Singleton();
System.out.println(instance1 == instance2);
// 因为加入了判断null,所以此时输出为true,只有一个对象
}
}
- 方式2(线程安全)
public class Singleton {
private Singleton(){
}
// 声明Singleton类型的变量
private static Singleton instance;
// 对外提供访问方式
public static synchronized Singleton getInstance(){
// 判断instance是否为null,如果为null,则还未创建对象
// 如果创建,直接返回instance
if(instance == null){
instance = new Singleton(); // 可认为是写操作
}
return instance;
}
}
// 假设此时是多线程的情况,线程1获得执行权,进入判断中,此时就算线程2拿到执行权
// 也只会等待线程1执行完
// synchronized修饰静态方法,是类锁:所以无论多少对象,只有1把锁,并且这还是单例模式,只有一个对象
- 方法3(线程安全)
// 由于getInstance()方法,绝大部分都是读操作,读操作是线程安全的,所以没必要让每个线程必须持有锁才能调用该方法,需要调整加锁的时机,由此产生:双重检查锁模式
// 而且此时需要将静态变量加上关键字volatile
// 原因是:
/*
在多线程的情况下,可能会出现空指针问题,问题问题的原因是JVM在实例化对象的时候进行优化和指令重排序操作,所以最好使用volatile关键字,该关键字可以保证可见性和有序性
因为new Singleton()不是原子指针,可以分为三步:(1)分配对象内存;(2)调用构造器方法,执行初始化;(3)将对象引用赋值给变量
JVM实际运行的时候,其中,2和3可能会发生重排序,因为2和3依托1,但是2和3关联不大
假设线程1获得锁进入创建对象实例中,这个时候发生了指令重排序(1,3,2),当线程1执行到第三步时,线程2刚好进入,由于此时对象已经不为null,所以线程2可以自由访问该对象,然而该对象还未初始化,所以线程2访问时将会发生异常。
*/
public class Singleton {
private Singleton(){
}
private static Singleton instance;
public static Singleton getInstance(){
// 第一次判断,如果不为null,不需要抢占锁,直接返回对象
if(instance == null){
synchronized (Singleton.class){
// 第二次判断
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
恶汉式-枚举方式
枚举类实现单例模式是比较好的单例实现模式,因为枚举类是线程安全的,并且只会装载一次,设计者充分利用了枚举的这个特性来实现单例模式。枚举的写法非常简单,而且枚举类型是所有单例实现中唯一不会被破坏的单例实现模式
public enum Singleton {
INSTANCE;
}
public class Client {
public static void main(String[] args) {
Singleton instance1 = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance1 == instance2); // true
}
}
单例模式存在的问题
-
破坏单例模式
- 序列化和反序列化
public class Singleton implements Serializable { private Singleton(){ } private static Singleton instance; public static Singleton getInstance(){ // 第一次判断,如果不为null,不需要抢占锁,直接返回对象 if(instance == null){ instance = new Singleton(); } return instance; } } public class Client { public static void main(String[] args) throws Exception{ writeObjectFIle(); // 两次读到的不一样 readObjectFromFile(); readObjectFromFile(); } public static void readObjectFromFile() throws Exception{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\a1.txt")); Singleton instance = (Singleton) ois.readObject(); System.out.println(instance); ois.close(); } public static void writeObjectFIle() throws Exception{ Singleton instance = Singleton.getInstance(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\a1.txt")); oos.writeObject((instance)); oos.close(); } }
- 反射:因为反射可以得到类的私有构造方法,获得后即可创建对象
-
解决方案
-
序列化和反序列化:在Singleton类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象
public class Singleton implements Serializable { private Singleton(){ } private static Singleton instance; public static Singleton getInstance(){ // 第一次判断,如果不为null,不需要抢占锁,直接返回对象 if(instance == null){ instance = new Singleton(); } return instance; } // 当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回 public Object readResolve(){ return instance; } } // 此时修正后,读取出来的是没有问题的,两次读取的内容均相同
-
反射破坏单例的解决方法:需要在Singleton类的私有构造方法中添加一个判断条件
public class Singleton{ private Singleton(){ if(instance != null){ throw new RuntimeException(); } } private static Singleton instance; public static Singleton getInstance(){ // 第一次判断,如果不为null,不需要抢占锁,直接返回对象 if(instance == null){ instance = new Singleton(); } return instance; } // 当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回 public Object readResolve(){ return instance; } }
-
-
Runtime使用的就是单例模式
工厂模式
工厂模式是三种工厂模式的总称:(1)简单工厂模式;(2)工厂方法模式;(3)抽象工厂模式。该模式的作用是实现创建者和调用者的分离。核心本质:(1)实例化对象不使用new,用工厂方法代替;(2)将选择实现类,创建对象统一管理和控制。从而将调用者跟实现类解耦。
- 简单工厂模式:用户不需要自己去创建对象,由工厂创建对象,并且由工厂执行创建对象的一系列复杂操作,用户不用关心复杂的创建过程,用户想要什么对象,只需要通过工厂创建就好了。
public interface Car{
void name();
}
public class wuLing implements Car{
@Override
void name(){
System.out.println("wuLing car");
}
}
public class Tesla implements Car{
@Override
void name(){
System.out.println("Tesla car");
}
}
public class CarFactory{
public static Car getCar(String car){
if(car.equals("wuLing")){
return new wuLing();
}else if(car.equals("Tesla")){
return new Tesla();
}else{
return null;
}
}
}
public class Consumer{
public static void main(String[] args){
/*
之前创建方式,相当于自己创建了一辆车。这样的话需要了解接口和实现类
Car car1 = new wuLing();
Car car2 = new Tesla();
*/
// 简单工厂模式:从工厂里面买车,只需要传入参数
Car car1 = CarFactory.getCar("wuLing");
}
}
// 以上模式有一个缺点,如果工厂需要增加车型,需要对CarFactory进行修改,这样不符合开闭原则
// 由此出现了工厂方法模式
- 工厂方法模式:为了满足不同用户的需求,工厂无法创建所有的宝马车系,于是又单独分出很多个具体的工厂,每个工厂都创建出一个用户需求的对象。用户需要指定哪个具体的工厂才能生产需要的车。
// 在完全满足开闭原则的情况下,实现工厂方法模式(实践中可能不会完全按照七大原则进行)
public interface CarFactory{
Car getCar();
}
public class TeslaFactory implements CarFactory{
@Override
Car getCar(){
return new Tesla();
}
}
public class WuLingFactory implements CarFactory{
@Override
Car getCar(){
return new Wuling();
}
}
public class Consumer{
public static void main(String[] args){
Car car1 = new WuLingFactory().getCar();
Car car2 = new TeslaFactory().getCar();
}
}
// 此时如何用户需要哈罗单车的时候,不需要修改原有代码,只需要增加一个Bike类实现CarFactory就可以了
// 但是每增加一个,就需要增加一个工厂类
抽象工厂模式
抽象工厂模式提供了一个创建一系列相关或者相互依赖对象的接口,无需指定它们具体的类
// 围绕一个超级工厂创建其他工厂
// 手机产品接口
public interface IphoneProdct{
void start();
void shutdown();
void callup();
void sendMS();
}
// 路由器接口
public interface IRouterProduct{
void start();
void shutdown();
void openwifi();
void setting();
}
// 小米手机
public class XiaomiPhone implements IphoneProduct{
@Override
public void start(){
System.out.println("开启小米手机");
}
@Override
public void shutdown(){
System.out.println("关闭小米手机");
}
@Override
public void callup(){
System.out.println("小米打电话");
}
@Override
public void sendMS(){
System.out.println("小米发短信");
}
}
// 华为手机
public class HuaweiPhone implements IphoneProduct{
@Override
public void start(){
System.out.println("开启华为手机");
}
@Override
public void shutdown(){
System.out.println("关闭华为手机");
}
@Override
public void callup(){
System.out.println("华为打电话");
}
@Override
public void sendMS(){
System.out.println("华为发短信");
}
}
// 小米手机
public class XiaomiPhone implements IphoneProduct{
@Override
public void start(){
System.out.println("开启小米手机");
}
@Override
public void shutdown(){
System.out.println("关闭小米手机");
}
@Override
public void callup(){
System.out.println("小米打电话");
}
@Override
public void sendMS(){
System.out.println("小米发短信");
}
}
// 华为手机
public class HuaweiPhone implements IphoneProduct{
@Override
public void start(){
System.out.println("开启华为手机");
}
@Override
public void shutdown(){
System.out.println("关闭华为手机");
}
@Override
public void callup(){
System.out.println("华为打电话");
}
@Override
public void sendMS(){
System.out.println("华为发短信");
}
}
// 华为手机
public class HuaweiPhone implements IphoneProduct{
@Override
public void start(){
System.out.println("开启华为手机");
}
@Override
public void shutdown(){
System.out.println("关闭华为手机");
}
@Override
public void callup(){
System.out.println("华为打电话");
}
@Override
public void sendMS(){
System.out.println("华为发短信");
}
}
// 华为手机
public class HuaweiPhone implements IphoneProduct{
@Override
public void start(){
System.out.println("开启华为手机");
}
@Override
public void shutdown(){
System.out.println("关闭华为手机");
}
@Override
public void callup(){
System.out.println("华为打电话");
}
@Override
public void sendMS(){
System.out.println("华为发短信");
}
}
// 华为路由器
public class HuaweiRouter implements IRouterProduct{
@Override
public void start(){
System.out.println("开启华为路由器");
}
@Override
public void shutdown(){
System.out.println("关闭华为路由器");
}
@Override
public void openwifi(){
System.out.println("打开华为wifi");
}
@Override
public void setting(){
System.out.println("华为路由器设置");
}
}
// 小米路由器跟上面代码类似,故省略
开始创建工厂,该工厂是抽象的抽象。
// 抽象产品工厂
public interface IProductFactory{
// 生产手机
IphoneProduct iphoneProduct();
// 生产路由器
IRouterProduct routerProduct();
}
// 小米工厂
public class XiaomiFactory implements IProductFactory{
@Override
public IphoneProduct iphoneProduct(){
return new XiaomiPhone();
}
@Override
public IRouterProduct routerProduct(){
return new XiaomiRouter();
}
}
// 华为工厂
public class HuaweiFactory implements IProductFactory{
@Override
public IphoneProduct iphoneProduct(){
return new HuaweiPhone();
}
@Override
public IRouterProduct routerProduct(){
return new HuaweiRouter();
}
}
// 客户端
public class Client{
public static void main(String[] args){
// 小米工厂
XiaomiFactory xiaomiFactory = new XiaomiFactory();
IphoneProduct iphoneProduct = xiaomiFactory.iphoneProduct();
IRouterProduct iRouterProduct = xiaomiFactory.routerProduct();
}
}
// 抽象工厂模式有纵向和横向,创建的超级工厂是抽象工厂的抽象工厂
// 该抽象工厂定义的是不同类型的产品的抽象工厂(可能有不同厂家)
// 比如上面手机和手机是一种类型的产品,但是由两个工厂实现的
// 上面方法存在缺点,比如小米工厂中新增加一个产品,需要先在超级工厂增加对应方法,后面实现的类都要进行修改,重写该方法。但是可以增加产品族,就是增加产品的品牌
代理模式
代理模式是SpringAOP的 底层。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
优点:
- 代理模式可以隐藏真实对象的实现细节,使得客户端无需知晓真实对象的工作方式和结构
- 通过代理类来间接访问真实类,可以在不修改真实类的情况下,对其进行扩展、优化或者添加安全措施
- 代理模式实现起来简单,易于扩展和维护,符合面向对象设计原则中的开闭原则
静态代理
代码编写期进行代理类和被代理类关联的代理方式,具体实现是创建一个代理类,通常需要实现与被代理类相同接口或继承被代理类。
角色分析:
- 抽象角色:一般会使用接口或抽象类来解决(租房)
- 真实角色:被代理的角色(房东)
- 代理角色:代理真实角色(中介),代理真实角色后,一般会做一些附属操作。
- 客户:访问代理对象的人(租房的人)
// 租房
public interface Rent{
public void rent();
}
// 房东
public class Host implements Rent{
@Override
public void rent(){
System.out.println("房东要出租房子");
}
}
// 客户
public class Client{
public static void main(String[] args){
// 未使用代理模式的租房
Host host = new Host();
host.rent();
// 使用代理模式后:通过代理租房,但是中介会做一些附属操作
Proxy proxy = new Proxy(host);
proxy.rent();
}
}
// 中介
public class Proxy implements Rent{
private Host host;
public Proxy(){
}
public Proxy(Host host){
this.host = host;
}
public void rent(){
seeHouse();
host.rent();
hetong();
fare();
}
// 看房(中介可以做一些附属操作)
public void seeHouse(){
System.out.println("中介带看房");
}
// 签合同
public void hetong(){
System.out.println("签租赁合同");
}
// 收中介费
public void fare(){
System.out.println("收中介费");
}
}
优点:
- 公共业务交给代理角色,实现了业务的分工
- 公共业务发生扩展的时候,方便集中管理
- 可以使业务逻辑更加清晰,真正的业务逻辑中不用去关注一些公共的业务
缺点:
- 一个真实角色就会产生一个代理角色:代码量会翻倍——开发效率会变低。因为真实角色需要实现抽象类。
动态代理
- 动态代理和静态代理角色一样
- 动态代理的代理类是动态生成的,不是直接写好的
- 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
- 基于接口——JDK动态代理
- 基于类——cglib
- Java字节码实现:javassist
JDK动态代理
- 它是在运行运行时动态生成代理类,也就是说我们在编写代码时并不知道具体代理的是什么类,而是在程序运行时动态生成。
- 对象必须实现一个或多个接口
InvocationHandler:由代理实例的调用处理程序实现的接口
Proxy:提供创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类
// 等我们会用这个类,自动生成代理类!
public class ProxyInvocationHandler implements InvocationHandler{
// 被代理的接口
private Rent rent;
public void setRent(Rent rent){
this.rent = rent;
}
// 生成得到代理类
public Object getProxy(){
// 通过反射得到类加载器,得到对应的接口
return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
}
// 重写的方法。处理代理实例,并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
// 动态代理的本质,就是使用反射机制实现
seeHouse();
Object result = method.invoke(rent, args);
fare();
return result;
}
public void seeHouse(){
System.out.println("中介带看房");
}
public void fare(){
System.out.println("支付中介费");
}
}
public class Client{
public static void main(String[] args){
// 真实角色
Host host = new Host();
// 代理角色:现在没有
ProxyInvocationHandler pih = new ProxyInvocationHandler();
// 通过调用程序处理角色来处理我们需要调用的接口对象
pih.setRent(host);
// 下面的proxy就是动态生成的代理类,而非我们自己固定写好的
// 注意返回值类型是强转成接口,而非是接口的实现类
Rent proxy = (Rent)pih.getProxy();
proxy.rent();
}
}
动态传入参数
// 等我们会用这个类,自动生成代理类!
public class ProxyInvocationHandler implements InvocationHandler{
private Object target;
// 传入参数
public void setTarget(Object target){
this.target = target;
}
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
// 重写的方法。处理代理实例,并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
// 动态代理的本质,就是使用反射机制实现
log(method.getName());
Object result = method.invoke(target, args);
return result;
}
public void log(String msg){
System.out.println("执行了" + msg + "方法");
}
}
public class Client{
public static void main(String[] args){
// 真实角色
UserServiceImpl userService = new UserServiceImpl();
// 代理角色:现在没有
ProxyInvocationHandler pih = new ProxyInvocationHandler();
// 通过调用程序处理角色来处理我们需要调用的接口对象
pih.setTarget(userService);
// 下面的proxy就是动态生成的代理类,而非我们自己固定写好的
UserServiceImpl proxy = (UserServiceImpl)pih.getProxy();
proxy.delete(); // 此时是delete方法
}
}
动态代理的好处:
- 一个动态代理类代理的是一个接口,一般就是对应的一类业务(如果还有一个UserServiceImplTwo实现了UserService,也重写了一些方法,那只需要改动上述程序中main函数的第一行即可,不需要再重新写一个代理类了)
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可
CGLIB动态代理
假设上述示例中没有定义UserService接口,只是定义一个普通的包含CRUD方法的类,此时JDK代理无法使用,因为JDK代理要求必须定义接口,对接口进行代理
- CGLIB是在运行时动态生成代理类的方式,使用的库是CGLIB,和JDK代理相比,它不是动态生成一个实现了接口的代理类,而是直接在内存中构建一个被代理类的子类,并重写父类的方法进行代理。
public class UserServiceITest {
public void create(){
System.out.println("创建成功");
}
public void read(){
System.out.println("创建成功");
}
}
public class ProxyHandlerTest implements MethodInterceptor {
private Object target;
public ProxyHandlerTest(Object target){
this.target = target;
}
/**
* Cglib通过Enhancer生成代理类,通过实现MethodInterceptor接口,实现intercept方法
*/
public Object getProxyObject(Class clazz){
// 创建Enhancer对象,类似于JDK代理中的Proxy类
Enhancer enhancer = new Enhancer();
// 设置父类的字节码对象
// cglib的代理类其实根据被代理对象生成的子类
enhancer.setSuperclass(clazz);
// 设置回调函数
// 表示代理对象的方法都会执行MethodInterceptor的子实现类的intercept方法。
enhancer.setCallback(this);
// 创建代理对象
Object proxyObject = enhancer.create();
return proxyObject;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
log(method.getName());
// 调用被代理对象的方法
Object result = method.invoke(target, objects);
return result;
}
public void log(String msg){
System.out.println("执行了" + msg + "方法");
}
}
public class Client {
public static void main(String[] args) {
// 创建被代理对象
UserServiceITest userServiceITest = new UserServiceITest();
// 创建代理对象处理程序
ProxyHandlerTest ph = new ProxyHandlerTest(userServiceITest);
// 获取代理对象
UserServiceITest proxyObject = (UserServiceITest) ph.getProxyObject(UserServiceITest.class);
// 调用被代理对象的方法
proxyObject.create();
}
}
JDK和CGLIB的区别
- JDK动态代理只能对实现了接口的类生成代理,而不能针对类
- CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法因为是继承,所以该类或方法最好不要声明成final
- JDK动态代理要求目标类必须要实现接口,而CGLIB动态代理则没有这个限制
- JDK动态代理相对于CGLIB动态代理来说,因为实现方式不同,生成的代理类效率会低