设计模式不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案
也称GoF设计模型
设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解
创建型模式
单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
结构型模式
适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
行为型模式
模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式
OOP七大原则(Object Oriented Programming 面向对象编程)
-
开闭原则:对扩展开放,对修改关闭
-
里氏替换原则:继承必须确保超类所拥有的性质在子类中仍然成立(尽量不要改变父类的方法,正方形不是长方形)
-
依赖倒置原则:要面向接口编程,不要面向实现编程
-
单一职责原则:控制类的粒度大小、讲对象解耦、提高其内聚性(一个方法尽可能干好一个事情,对象的职责要尽量单一)
-
接口隔离原则:要为各个类建立他们需要的专用接口
-
迪米特法则: 只与你的直接朋友交谈,不跟“陌生人”说话(A <-> B <-> C A发给C的请求要经过B,不能直接与C交谈,为了解耦)
-
合成复用原则: 尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现(A是B的成员变量 has a的关系,继承是is a的关系)
单例模式
通过单例模式,可以保证系统中一个类只有一个实例,主要应用在资源共享、控制资源之间交流
单例模式可以分为饿汉式、懒汉式、静态内部类、枚举
单例模式的特点:
-
构造器私有
-
单例类只有一个实例对象;
-
该单例对象必须由单例类自行创建;
-
单例类对外提供一个访问该单例的全局访问点。
饿汉式
定义变量的时候就直接初始化,这样容易浪费空间
//饿汉式
public class Hungry {
//构造器私有化
private Hungry() {
}
//一启动类就直接加载
//可能会造成内存浪费
byte[] byte1=new byte[1024*1024];
byte[] byte2=new byte[1024*1024];
byte[] byte3=new byte[1024*1024];
byte[] byte4=new byte[1024*1024];
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance()
{
return HUNGRY;
}
}
静态内部类
-
通过静态内部类实现单例,不会在一开始就初始化,而是在getInstance()被调用的时候再初始化对象
-
但无论是静态内部类还是饿汉式,用反射都可以获取私有构造器而破坏单例
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.instance;
}
private static class InnerClass{
private static final Holder instance = new Holder();
}
}
通过反射获取构造器并且创建两个对象
//通过反射获取构造器创建两个对象
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Constructor<Holder> constructor = Holder.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
Holder instance1 = constructor.newInstance(null);
Holder instance2 = constructor.newInstance(null);
//两个对象的hashCode不同,说明创建了两个不同的对象,反射可以破坏静态内部类模式的单例
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
懒汉式
-
定义static变量存储实例
-
定义getInstance()方法来创建并获取实例
-
避免了一开始就完成初始化
双重检测模式的懒汉式单例,DCL懒汉式
三层是给构造方法加判断
//懒汉式
public class LazyMan {
private LazyMan(){
synchronized (LazyMan.class) {
if(lazyMan!=null){
throw new RuntimeException("do not try to use refection");
}
}
}
private volatile static LazyMan lazyMan;
//双重检测模式的懒汉式单例,DCL懒汉式
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized (LazyMan.class) {
if(lazyMan==null){
System.out.println("new instance");
lazyMan = new LazyMan();
/**
* new 的过程
* 1. 分配内存空间
* 2. 执行构造方法,初始化对象
* 3. 把这个对象指向这个空间
*
* 但是实际执行顺序可能会不一样比如132
* 在LazyMan前加上volatile关键字,保证123的执行顺序
* volatile关键字保证了变量先写后读的顺序
*/
}
}
}
return lazyMan;
}
}
测试1:正常调用getInstance,结果一致
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
LazyMan instance = LazyMan.getInstance();
LazyMan instance2 = LazyMan.getInstance();
System.out.println(instance.hashCode());
System.out.println(instance2.hashCode());
}
测试2:第二个对象用反射创建
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
LazyMan instance = LazyMan.getInstance();
//反射通过构造器创建对象
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
LazyMan instance2 = constructor.newInstance();
System.out.println(instance.hashCode());
System.out.println(instance2.hashCode());
}
//报错信息,看Caused by
new instance
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.catsic.cases.util.Test2.main(Test2.java:39)
Caused by: java.lang.RuntimeException: do not try to use refection
at com.catsic.cases.util.Test2.<init>(Test2.java:13)
... 5 more
会被校验抛出异常,阻止了反射创建对象
测试3:用反射创建两个实例,或者将测试2改为第一个对象用反射创建,会发现反射还是破坏了单例
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
//反射会破坏单例
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
LazyMan instance = constructor.newInstance();
LazyMan instance2 = constructor.newInstance();
System.out.println(instance.hashCode());
System.out.println(instance2.hashCode());
}
枚举
枚举类不允许反射来干预
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
//通过反射试图创建两个对象
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
EnumSingle instance2 = constructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
//报错信息
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.catsic.cases.util.Test2.main(Test2.java:21)
程序报错,不允许通过反射创建枚举类实例,因此使用枚举实现单例可以防止反射破坏
总结各种实现单例的方式:
-
饿汉式一开始就初始化了实例,容易浪费空间资源
-
静态内部类式避免了一开始就初始化
-
饿汉式和静态内部类式在多线程环境下都是线程安全的,但它们都会被反射所破坏
-
懒汉式如果不加双重检测是线程不安全的,必须加上双重检测模式才能在多线程并发下保证单例
-
懒汉式终究无法完全防止反射的破坏,但枚举实现单例可以完全防止反射破坏
java各个东西的默认修饰符
enum 常量 public static final
interface 方法:public abstract 变量:public static final
工厂模式
实现了创建者和调用者的分离
分为:简单工厂模式、工厂方法模式、抽象工厂模式
简单工厂模式 (静态工厂模式)
用来生产同一等级结构中的任意产品(对于新增产品,要求覆盖已有代码)
public class CarFactory {
public static Car getCar(String car){
if(car.equals("五菱")){
return new WuLing();
}else if(car.equals("tesla")){
return new Tesla();
}else{
return null;
}
}
//方法二
public static Car getWuLing(){
return new WuLing();
}
}
public class Consumer {
public static void main(String[] args) {
CarFactory.getCar("tesla").name();
}
}
public interface Car {
void name();
}
public class WuLing implements Car {
@Override
public void name() {
System.out.println("五菱宏光");
}
}
public class Tesla implements Car {
@Override
public void name() {
System.out.println("特斯拉");
}
}
简单来说,就是不自己new对象,交给factory来创建对象,交给factory统一管理,但是有个缺点,如果新加一个车,就需要改factory,加实现类,比较麻烦,不符合开闭原则,但是大部分时候用的都是简单工厂模式,因为为了满足开闭原则会消耗很多的资源
工厂方法模式
用来生产同一等级结构中的固定产品(支持增加任意产品)
public interface CarFactorys {
void getCar();
}
public class WuLingFactory implements CarFactorys {
@Override
public void getCar() {
System.out.println("五菱宏光");
}
}
public class Consumer {
public static void main(String[] args) {
new WuLingFactory().getCar();
}
}
工厂方法模式可以快速的实现产品的横向扩展,给每个产品加一个工厂,消费者直接创建对应产品的工厂来进行生产对象,很明显,这样的资源消耗比简单工厂多很多
结构复杂度,代码复杂度,编程复杂度,管理上的复杂度,都是简单工厂模式更加简便
根据设计原则来说,应该使用工厂方法模式
根据实际业务来说,应该使用简单工厂模式
抽象工厂模式
围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂
提供了一个创建一系列相关或者相互依赖对象的接口,无需指定他们具体的类
IphoneProduct(i)、IRouteProduct(i) -> 同一产品等级下的产品接口
xiaomiIphone、xiaomiIRouter、huaweiiphone、huaweiirouter -> 具体产品
IProductFactory(i) -> 抽象的产品工厂
xioamifactory、huaweifactory -> 具体的工厂
public class Coustom {
public static void main(String[] args) {
XiaomiFactory xmf = new XiaomiFactory();
xmf.iPhoneProduct().start();
}
}
public class XiaomiFactory implements IProductFactory {
@Override
public IphoneProduct iPhoneProduct() {
return new XiaomiIphoneProduct();
}
@Override
public IRouteProduct iRouteProduct() {
return new XiaomiIRouteProduct();
}
}
public interface IProductFactory {
IphoneProduct iPhoneProduct();
IRouteProduct iRouteProduct();
}
public class XiaomiIphoneProduct implements IphoneProduct {
@Override
public void start() {
System.out.println("小米手机开机");
}
@Override
public void shutDown() {
System.out.println("小米手机关机");
}
}
public class XiaomiIRouteProduct implements IRouteProduct {
@Override
public void startWifi() {
System.out.println("小米路由器开机");
}
@Override
public void shutDownWifi() {
System.out.println("小米路由器关机");
}
}
public interface IphoneProduct {
void start();
void shutDown();
}
public interface IRouteProduct {
void startWifi();
void shutDownWifi();
}
产品接口 -> 实现产品的具体类 -> 产品工厂接口 -> 产品工厂实现类
主要是有一个抽象的产品工厂接口,是生产工厂的工厂
优点:
-
具体产品在应用层的代码隔离,无需关心创建的细节
-
将一个系列的产品统一到一起创建
缺点:
-
规定了所有可能被创建的产品集合,产品族中扩展新的产品困难
-
增加了系统的抽象性和理解难度
工厂模式小结:
-
简单工厂模式(静态工厂模式),虽然某种程度上不符合设计原则,但实际使用最多
-
工厂方法模式,不修改已有类的前提下,通过增加新的工厂类实现扩展
-
抽象工厂模式,不可以增加产品,可以增加产品族
建造者模式
建造者模式也属于创建型模式,它提供了一种创建对象的最佳方式
定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
主要作用:在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象
用户只需要给出指定复杂对象的类型和内容,建造者模式负责按顺序创建复杂的对象(把内部的建造过程和细节隐藏起来)
原型模式
-
实现Cloneable接口
-
重写clone()方法
分为浅克隆和深克隆
浅克隆:
public class Video implements Cloneable {
private String name;
private Date createTime;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public Video() {
}
public Video(String name, Date createTime) {
this.name = name;
this.createTime = createTime;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "Video{" +
"name='" + name + '\'' +
", createTime=" + createTime +
'}';
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Date date = new Date();
Video video1 = new Video("视频1",date);
video1.setName("视频2");
Video video2 = (Video)video1.clone();
System.out.println("v1="+video1+",v1 ->"+video1.hashCode());
System.out.println("v2="+video2+",v2 ->"+video2.hashCode());
System.out.println("=================");
date.setTime(23232323);
System.out.println("v1="+video1+",v1 ->"+video1.hashCode());
System.out.println("v2="+video2+",v2 ->"+video2.hashCode());
}
}
根据结果可以看出来,修改date之后,v1和v2的值都发生了变化,这就是以为浅克隆是把对象的指向也克隆了
深克隆:(序列化 反序列化 改造clone方法)
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone();
//将对象的属性也进行克隆
Video v = (Video)obj;
v.createTime = (Date) this.createTime.clone();
return obj;
}
spring Bean : 单例模式 原型模式
适配器模式
从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题
目标接口:客户所期待的接口,目标可以是具体的或者抽象的类,也可以是接口
需要适配的类:需要适配的类或者适配者类
适配器:通过包装一个需要适配的对象,把原接口转换成目标对象
//类适配器 继承网线类
//具体的适配器
public class Adapter1 extends Adaptee implements Adapter {
@Override
public void netToUSB() {
super.net();
}
}
//对象适配器 组合网线类 推荐使用
public class Adapter2 implements Adapter {
private Adaptee adaptee;
public Adapter2(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void netToUSB() {
adaptee.net();
}
}
桥接模式
桥接模式是将抽象部分与他的实现部分分离,使他们都可以独立的变化
不符合类的单一职责原则,一个类负责了两个功能
//品牌
public interface Brand {
void info();
}
public class Lenovo implements Brand {
@Override
public void info() {
System.out.print("联想");
}
}
public class Apple implements Brand {
@Override
public void info() {
System.out.print("苹果");
}
}
public abstract class Computer {
protected Brand brand;
//自带品牌
public Computer(Brand brand) {
this.brand = brand;
}
void info(){
brand.info();
}
}
class Desktop extends Computer{
public Desktop(Brand brand) {
super(brand);
}
@Override
void info() {
super.info();
System.out.println("台式机");
}
}
public class Test {
public static void main(String[] args) {
Computer computer = new Desktop(new Lenovo());
computer.info();
}
}
静态代理
角色分析:
-
抽象角色:一般会使用接口或者抽象类来解决
-
真实角色:被代理的角色
-
代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
-
客户:访问代理对象的人
//租房
public interface Rent {
void rent();
}
//房东要出租房子
public class Host implements Rent{
@Override
public void rent(){
System.out.println("房东要出租房子");
}
}
//中介帮房东出租房子
public class HouseProxy implements Rent{
private Host host;
public HouseProxy(Host host) {
this.host = host;
}
@Override
public void rent(){
seeHouse();
host.rent();
fare();
}
public void seeHouse(){
System.out.println("看房");
}
public void fare(){
System.out.println("收中介费");
}
}
public class Test {
public static void main(String[] args) {
Host host = new Host();
HouseProxy houseProxy = new HouseProxy(host);
houseProxy.rent();
}
}
代理模式的好处:
-
可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
-
公共业务就交给代理角色,实现了业务的分工
-
公共业务发生扩展的时候,方便集中管理
缺点:
-
一个真实角色就会产生一个代理角色;代码量会翻倍,开发效率变低
动态代理
-
动态代理和静态代理角色一样 (代理角色 真实角色 抽象角色)
-
动态的代理的代理类是动态生成的,不是我们直接写好的
-
动态代理分为两大类:基于接口的动态代理,基于类的动态代理
-
基于接口 -- jdk动态代理
-
基于类 -- cglib
-
java字节码实现 -- javaAssist
-
需要了解两个类:Proxy --> 代理类 InvocationHandle --> 调用处理程序
InvocationHandle
调用处理程序实现的接口
Proxy
提供创建动态代理类和实例的静态方法
动态代理模式的好处:
-
可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
-
公共业务就交给代理角色,实现了业务的分工
-
公共业务发生扩展的时候,方便集中管理
-
一个动态代理类代理的是一个接口,一般就是对应一类业务
-
一个动态代理了可以代理多个类,只要实现了同一接口即可
//租房
public interface Rent {
void rent();
}
//房东
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东要出租房子");
}
}
public class ProxyInvocationHandle 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);
}
//处理代理实例,并返回结果
@Override
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();
ProxyInvocationHandle pih = new ProxyInvocationHandle();
pih.setRent(host);
Rent proxy = (Rent)pih.getProxy();
proxy.rent();
}
}
动态代理万能工具类:
public class ProxyInvocationHandleUtil implements InvocationHandler {
private Object target;
public ProxyInvocationHandleUtil(Object target) {
this.target = target;
}
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
@Override
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 + "方法");
}
}
个人理解,proxy.rent();
调用的就是invoke方法,参数里proxy就是这个proxy,method就是rent,也就是可以定位到是哪个实现类中的哪个方法
静态代理和动态代理的区别
动态代理模式中,动态代理类是动态生成的,不需要一个代理类对应一个真实角色,比如说房东租房这个案例,动态代理类中,实现类是传进去的,根据传进去的实现类来生成动态代理类,这个动态代理类可以强转成真实角色类,调用方法,所以只需要实现对应的接口就可以用一个动态代理类来解决一类业务
而静态代理模式中,代理类中显式实现抽象角色,也就是实现了租房的接口,每类业务就需要一个对应的动态代理类