类图(Class diagram)是显示了模型的静态结构,特别是模型中存在的类、类的内部结构以及它们与其他类的关系等。类图不显示暂时性的信息。类图是面向对象建模的主要组成部分。
以图形的方式描述类与类之间的关系
用于表示一类对象与另一类对象之间的联系,如老师和学生、师傅和徒弟、丈夫和妻子等。关联关系是类与类之间最常用的一种关系,分为一般关联关系、聚合关系和组合关系。
聚合关系是关联关系的一种,是强关联关系,是整体和部分之间的关系。
聚合关系也是通过成员对象来实现的,其中成员对象是整体对象的一部分,但是成员对象可以脱离整体对象而独立存在。例如,学校与老师的关系,学校包含老师,但如果学校停办了,老师依然存在。
在 UML 类图中,聚合关系可以用带空心菱形的实线来表示,菱形指向整体。下图所示是大学和教师的关系图:
组合表示类之间的整体与部分的关系,但它是一种更强烈的聚合关系。
在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在。例如,头和嘴的关系,没有了头,嘴也就不存在了。
在 UML 类图中,组合关系用带实心菱形的实线来表示,菱形指向整体。下图所示是头和嘴的关系图:
依赖关系是一种使用关系,它是对象之间耦合度最弱的一种关联方式,是临时性的关联。在代码中,某个类的方法通过局部变量、方法的参数或者对静态方法的调用来访问另一个类(被依赖类)中的某些方法来完成一些职责。
在 UML 类图中,依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类。下图所示是司机和汽车的关系图,司机驾驶汽车:
在软件开发中,为了提高软件系统的可维护性和可复用性,增加软件的可扩展性和灵活性,程序员要尽量根据6条原则来开发程序,从而提高软件开发效率、节约软件开发成本和维护成本。
**对扩展开放,修改关闭。**在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。
想要达到这样的效果,我们需要使用接口和抽象类。
因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。
**任何基类可以出现的地方,子类一定可以出现。**通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能。换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
继承复用虽然有简单和易实现的优点,但它也存在以下缺点:
采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点:
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。
这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。
保证在内存中只有一个实例
不管调多少次getInstance(),拿到的都只有一个实例。他是线程安全的,因为JVM保证每个类只会被加载到内存中一次
懒汉式会造成线程不安全问题,假设第一个线程判断instancenull,还没来得及new对象,第二个线程也判断为instancenull,也去new对象,这时候就出现了,两个对象,不安全
懒汉式线程安全改进,需要在instance前加valtile修饰,进制指令重排
在外部类加载的时候,静态内部类不会被加载。当调用getInstance()时,静态内部类才被加载。因此,不但实现了懒加载,还实现了只有一个实例
枚举类没有构造方法
public class Test { public static void main(String[] args) throws Exception { //往文件中写对象 //writeObject2File(); //从文件中读取对象 Singleton s1 = readObjectFromFile(); Singleton s2 = readObjectFromFile(); //判断两个反序列化后的对象是否是同一个对象 System.out.println(s1 == s2); } private static Singleton readObjectFromFile() throws Exception { //创建对象输入流对象 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Think\\Desktop\\a.txt")); //第一个读取Singleton对象 Singleton instance = (Singleton) ois.readObject(); return instance; } public static void writeObject2File() throws Exception { //获取Singleton类的对象 Singleton instance = Singleton.getInstance(); //创建对象输出流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Think\\Desktop\\a.txt")); //将instance对象写出到文件中 oos.writeObject(instance); } }
public class Test { public static void main(String[] args) throws Exception { //获取Singleton类的字节码对象 Class clazz = Singleton.class; //获取Singleton类的私有无参构造方法对象 Constructor constructor = clazz.getDeclaredConstructor(); //取消访问检查 constructor.setAccessible(true); //创建Singleton类的对象s1 Singleton s1 = (Singleton) constructor.newInstance(); //创建Singleton类的对象s2 Singleton s2 = (Singleton) constructor.newInstance(); //判断通过反射创建的两个Singleton对象是否是同一个对象 System.out.println(s1 == s2); } }
public class Singleton implements Serializable { //私有构造方法 private Singleton() {} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } //对外提供静态方法获取该对象 public static Singleton getInstance() { return SingletonHolder.INSTANCE; } /** * 下面是为了解决序列化反序列化破解单例模式 */ private Object readResolve() { return SingletonHolder.INSTANCE; } }
public class Singleton { //私有构造方法 private Singleton() { /* 反射破解单例模式需要添加的代码 */ if(instance != null) { throw new RuntimeException(); } } private static volatile Singleton instance; //对外提供静态方法获取该对象 public static Singleton getInstance() { if(instance != null) { return instance; } synchronized (Singleton.class) { if(instance != null) { return instance; } instance = new Singleton(); return instance; } } }
Runtime使用的是饿汉式实现单例的
如果创建的时候直接new该对象,就会对该对象耦合严重,假如要更换对象,所有new对象的地方都需要修改一遍,违背了软件设计的开闭原则。如果我们使用工厂来生产对象,就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦。
优点:
缺点:
升级可以使用静态工厂模式
定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。
优点:
缺点:
理解同一级别和同一产品族
是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
优点:
缺点:
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
对象的创建非常复杂,可以使用原型模式快捷的创建对象
性能和安全要求比较高
将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。(比如更换内存条组装成为不同的主机)
优点:
缺点:
在开发中还有一个常用的使用方式,就是当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码可读性会非常差,而且很容易引入错误,此时就可以利用建造者模式进行重构。(如果参数更多呢?代码的可读性及使用的成本就是比较高。)(重构后的代码在使用起来更方便,某种程度上也可以提高开发效率。从软件设计上,对程序员的要求比较高。)
又被称为发布-订阅(Publish/Subscribe)模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。
Java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象。
//卖票接口 public interface SellTickets { void sell(); } //火车站 火车站具有卖票功能,所以需要实现SellTickets接口 public class TrainStation implements SellTickets { public void sell() { System.out.println("火车站卖票"); } } //代理工厂,用来创建代理对象 public class ProxyFactory { private TrainStation station = new TrainStation(); public SellTickets getProxyObject() { //使用Proxy获取代理对象 /* newProxyInstance()方法参数说明: ClassLoader loader : 类加载器,用于加载代理类,使用真实对象的类加载器即可 Class<?>[] interfaces : 真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口 InvocationHandler h : 代理对象的调用处理程序 */ SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance( station.getClass().getClassLoader(), station.getClass().getInterfaces(), new InvocationHandler() { /* InvocationHandler中invoke方法参数说明: proxy : 代理对象 method : 对应于在代理对象上调用的接口方法的 Method 实例 args : 代理对象调用接口方法时传递的实际参数 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("代理点收取一些服务费用(JDK动态代理方式)"); //执行真实对象 Object result = method.invoke(station, args); return result; } }); return sellTickets; } } //测试类 public class Client { public static void main(String[] args) { //获取代理对象 ProxyFactory factory = new ProxyFactory(); SellTickets proxyObject = factory.getProxyObject(); proxyObject.sell(); } }
ProxyFactory不是代理模式中所说的代理类,而代理类是程序在运行过程中动态的在内存中生成的类。通过阿里巴巴开源的 Java 诊断工具(Arthas【阿尔萨斯】)查看代理类的结构:
//程序运行过程中动态生成的代理类 public final class $Proxy0 extends Proxy implements SellTickets { private static Method m3; public $Proxy0(InvocationHandler invocationHandler) { super(invocationHandler); } static { m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]); } public final void sell() { this.h.invoke(this, m3, null); } } //Java提供的动态代理相关类 public class Proxy implements java.io.Serializable { protected InvocationHandler h; protected Proxy(InvocationHandler h) { this.h = h; } } //代理工厂类 public class ProxyFactory { private TrainStation station = new TrainStation(); public SellTickets getProxyObject() { SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(), station.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("代理点收取一些服务费用(JDK动态代理方式)"); Object result = method.invoke(station, args); return result; } }); return sellTickets; } } //测试访问类 public class Client { public static void main(String[] args) { //获取代理对象 ProxyFactory factory = new ProxyFactory(); SellTickets proxyObject = factory.getProxyObject(); proxyObject.sell(); } }
执行流程如下:
1.在测试类中通过代理对象调用sell()方法
2.根据多态的特性,执行的是代理类($Proxy0)中的sell()方法
3.代理类(Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法
4.invoke方法通过反射执行了真实对象所属类(TrainStation)中的sell()方法
看一下CGLib的基本结构,下图所示,代理类去继承目标类,每次调用代理类的方法都会被方法拦截器拦截,在拦截器中才是调用目标类的该方法的逻辑,结构还是一目了然的;
//目标类 public class Dog{ final public void run(String name) { System.out.println("狗"+name+"----run"); } public void eat() { System.out.println("狗----eat"); } } //方法拦截器 public class MyMethodInterceptor implements MethodInterceptor{ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("这里是对目标类进行增强!!!"); //注意这里的方法调用,不是用反射哦!!! Object object = proxy.invokeSuper(obj, args); return object; } } //测试类 import net.sf.cglib.core.DebuggingClassWriter; import net.sf.cglib.proxy.Enhancer; public class CgLibProxy { public static void main(String[] args) { //在指定目录下生成动态代理类,我们可以反编译看一下里面到底是一些什么东西 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\java\\java_workapace"); //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数 Enhancer enhancer = new Enhancer(); //设置目标类的字节码文件 enhancer.setSuperclass(Dog.class); //设置回调函数 enhancer.setCallback(new MyMethodInterceptor()); //这里的creat方法就是正式创建代理类 Dog proxyDog = (Dog)enhancer.create(); //调用代理类的eat方法 proxyDog.eat(); } }
我们就打开xxx.java文件,稍微进行整理一下,我们可以看到对于eat方法,在这个代理类中对应会有eat 和CGLIBeat$0这两个方法;其中前者则是我们使用代理类时候调用的方法,后者是在方法拦截器里面调用的,换句话来说当我们代码调用代理对象的eat方法,然后会到方法拦截器中调用intercept方法,该方法内则通过proxy.invokeSuper调用CGLIBeat$0这个方法,不要因为方法名字太长了就觉得难,其实原理很简单。。。(顺便一提,不知道大家有没有发现代理类中只有eat方法,没有run方法,因为run方法被final修饰了,不可被重写,所以代理类中就没有run方法,这里要符合java规范!!!)
为什么要用这种机制呢?直接用反射多好啊,但是我们知道反射虽然很好用,但是和直接new对象相比,效率有点慢,于是就有了这种机制
public class test10 { //这里,tt可以看作目标对象,fc可以看作是代理对象;首先根据代理对象的getIndex方法获取目标方法的索引, //然后再调用代理对象的invoke方法就可以直接调用目标类的方法,避免了反射 public static void main(String[] args){ Test tt = new Test(); Test2 fc = new Test2(); int index = fc.getIndex("f()V"); fc.invoke(index, tt, null); } } class Test{ public void f(){ System.out.println("f method"); } public void g(){ System.out.println("g method"); } } class Test2{ public Object invoke(int index, Object o, Object[] ol){ Test t = (Test) o; switch(index){ case 1: t.f(); return null; case 2: t.g(); return null; } return null; } //这个方法对Test类中的方法建立索引 public int getIndex(String signature){ switch(signature.hashCode()){ case 3078479: return 1; case 3108270: return 2; } return -1; } }
当我们去调用方法一的时候,在代理类中会先判断是否实现了方法拦截的接口,没实现的话直接调用目标类的方法一;如果实现了那就会被方法拦截器拦截,在方法拦截器中会对目标类中所有的方法建立索引,其实大概就是将每个方法的引用保存在数组中,我们就可以根据数组的下标直接调用方法,而不是用反射;索引建立完成之后,方法拦截器内部就会调用invoke方法(这个方法在生成的FastClass中实现),在invoke方法内就是调用CGLIB 方 法 一 方法一 方法一这种方法,也就是调用对应的目标类的方法一;
一般我们要添加自己的逻辑就是在方法拦截器那里。。。。
如果去欧洲国家去旅游的话,他们的插座如下图最左边,是欧洲标准。而我们使用的插头如下图最右边的。因此我们的笔记本电脑,手机在当地不能直接充电。所以就需要一个插座转换器,转换器第1面插入当地的插座,第2面供我们充电,这样使得我们的插头在当地能使用。生活中这样的例子很多,手机充电器(将220v转换为5v的电压),读卡器等,其实就是使用到了适配器模式。
定义:
适配器模式分为类适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。
对象适配器模式可釆用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。
在不改变现有对象结构的情况下,动态的给该对象增加一些职责的模式(即增加其额外的功能)
•当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
不能采用继承的情况主要有两类:
◾第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;
◾第二类是因为类定义不能继承(如final类)
•在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
•当对象的功能要求可以动态地添加,也可以再动态地撤销时。
•相同点:
◾都要实现与目标类相同的业务接口
◾在两个类中都要声明目标对象
◾都可以在不修改目标类的前提下增强目标方法
•不同点:
◾目的不同 装饰者是为了增强目标对象 静态代理是为了保护和隐藏目标对象
◾获取目标对象构建的地方不同 装饰者是由外界传递进来,可以通过构造方法传递 静态代理是在代理类内部创建,以此来隐藏目标对象