单例设计模式1
一、饿汉式
单例设计模式,简单说:一个类只有一个实例对象。
饿汉式是指,这个类一旦加载,这个类的实例就被创建。
单例设计模式核心:因为内存、所以性能。
饿汉式代码步骤:1.构造函数私有化,2.内部创建本类静态实例,3.对外提供公共的访问实例的方法。
饿汉式是线程安全的,因为类只会加载一次,加载的时候只会创建一份instance对象。
public class Single {
private static final Single instance = new Single();
private Single(){}
public static Single getInstance(){
return instance;
}
}
二、懒汉式
懒汉式是指:只有调用这个单例类得时候,才去创建这个单例类的实例对象。比如下面的代码,但是下面的代码有线程安全问题。两个线程同时进入if判断,也就创建了两次这个对象,不满足需求。
public class Single {
private static Single instance = null;
private Single(){}
public static Single getInstance(){
if(instance == null){
instance = new Single();
}
return instance;
}
}
解决办法如下代码:把getInstance()方法设为同步。这样做的好处是线程安全了,但是坏处就是每次调用getInstance()方法都要去判断同步锁,这样会浪费性能。实际上只是需要在第一次创建实例的时候,需要同步。
public synchronized static Single getInstance(){
if(instance == null){
instance = new Single();
}
return instance;
}
改进以上的代码的方法就是“双重检验锁”。
//A,B线程同时调用getInstance()方法
public static Single getInstance(){
if(instance == null){
synchronized (Single.class){
if(instance==null){
instance = new Single();
}
}
}
return instance;
}
}
注:在多线程情况下,JVM虚拟机可能会出现指令优化。在单例对象调用构造函数之前,就已经分配内存。这个时候,instance指向的其实是一个未初始化好的单例对象。所以,这个时候,我们需要在instance引用前面加上volatile关键字,禁止指令重排序,保证instance引用初始化的时候,单例对象已经被初始化。
为了解决上述的问题,可以使用volatile关键字进行修饰 instance 字段。volatile 关键字在这里的含义就是禁止指令的重排序优化(另一个作用是提供内存可见性),从而保证 instance 字段被初始化时,单例对象已经被完全初始化。
下面重排序过程务必能够手写出来。
instance = new Singleton(); 这一行代码可以拆分成三句。
memory = allocate(); //1.分配对象的内存空间
initInstance(memory); //2.初始化对象
instance = memroy; //3.设置instance引用指向这块内存
三、使用嵌套类的懒汉式
public class Single {
private Single(){}
private static class SingleFactory{
private static final Single instance = new Single();
}
public static final Single getInstance(){
return SingleFactory.instance;
}
}
这种方法是使用内部类。原理是:加载某各类由调用这个类的静态成员触发。这个内部类的静态成员就是本类的唯一的实例对象。
这样,既保证了一个类只有一个实例对象,又是懒汉式的,还没有线程安全问题(原因:一个类只会被JVM加载一次)。
四、优缺点
单例模式的优点:
1.节约内存。一个类只有一个实例,内存开销比较小。
2.提高系统性能。如果一个类对象的创建需要读取许多配置文件,可以在系统创建的时候,创建此对象,让它一直驻留在内存中。
单例模式的缺点:
1.扩展性很差。使用单例模式的类一般没有接口,没有面向接口编程,扩展比较困难,一般扩展需要修改代码。
五、使用场景
1.需要频繁实例化的对象然后销毁的对象。
2.创建对象时耗时过多或者是读取很多配置文件的对象。
刻意练习
刻意练习
(1)单例设计模式高性能的写法
(2)单例设计模式双重锁写法
(3)饿汉式写法
(4)单例设计模式应用场景是什么
(5)优缺点
策略设计模式2
一、基本定义
“针对”一组算法,“将”每一个算法封装到具有相同接口的独立的类中,“从而”使它们可以相互替换。
核心:策略实现相同接口,可以相互替换。即封装的各个算法地位平等,它们具有相同的接口,可以相互进行替换。
二、速记
策略设计模式的重点不是如何实现算法,而是如何组织、调用这些算法。策略设计模式让程序的结构更加灵活,让系统具有更好的灵活性和扩展性,降低需求变动的维护成本。
核心:封装地位相同的各种算法,让它们可以动态的切换。
优点:扩展性很好,增加算法只需要增加一个新的策略实现。
缺点:随着策略越来越多,维护这些的策略也很困难。
场景:用于计算公式经常变动的业务场景,比如学生的学分GPA的计算。
注入:在业务中提供一个策略工厂。在工厂的提供的方法中通过条件来判断应该返回哪一个策略,这样业务代码就不用变动。
三、代码实现
策略设计模式代码有三个主体。
1.持有抽象策略的引用的类
2.抽象策略类
3.具体策略类
public class Context {
private Stategy stategy;
public Context(Stategy stategy) {
this.stategy = stategy;
}
public int result(){
return stategy.function();
}
//主函数可以视为要被调用的环境,例如Service中,或者是Controller中
public static void main(String[] args) {
Context context = new Context(new Stategy1());
System.out.println(context.result());
}
}
//抽象策略
interface Stategy{
int function();
}
//具体三种策略实现
class Stategy1 implements Stategy{
@Override
public int function() {
return 1;
}
}
class Stategy2 implements Stategy{
@Override
public int function() {
return 2;
}
}
class Stategy3 implements Stategy{
@Override
public int function() {
return 3;
}
}
这个例子里面Context类持有着抽象策略的引用,由构造函数接收抽象策略实现类。Context调用的result()方法,其实就是包裹的策略的function()方法。根据具体情况,选择不同的策略的实现类,传递给Context类的构造器。
范例二:策略模式与简单工厂模式结合
这种方式更加适合与MVC模式。Controller代码中不直接生成相关策略,根据前台传递过来的参数,交由Context类的构造函数,在Context类的内部进行选择具体的实现类。Controller层里面的代码只需要知道Context类,根本不需要知道具体的策略类。在发生需求变动的时候,我们修改的类是Context类的构造函数,修改switch()语句,因此Controller层的代码不需要变动。代码如下:
public class Context {
private Stategy stategy;
public Context(int i) {
switch (i){
case 1:{
stategy = new Stategy1();
break;
}
case 2:{
stategy = new Stategy2();
break;
}
case 3: {
stategy = new Stategy3();
break;
}
default:{
stategy = new Stategy1();
}
}
}
public int result(){
return stategy.function();
}
//此主函数可以看作是要实际应用的环境,比如某个Controller中
public static void main(String[] args) {
Context context = new Context(3);//这个构造函数的参数由前端传递过来
System.out.println("结果是:"+context.result());
}
}
//策略
interface Stategy{
int function();
}
//具体实现三种
class Stategy1 implements Stategy{
@Override
public int function() {
return 1;
}
}
class Stategy2 implements Stategy{
@Override
public int function() {
return 2;
}
}
class Stategy3 implements Stategy{
@Override
public int function() {
return 3;
}
}
刻意练习
(1)策略设计模式的核心。
(2)策略设计模式的优点。
(3)策略设计模式的缺点。
(4)使用场景。
(5)类图
(6)策略类怎么注入到业务中
工厂设计模式3
一、简单工厂模式
定义:简单工厂就是用来创建其它类的实例。
简单工厂模式代码三个主体:1.工厂类 2.抽象产品 3.具体产品
从上述代码来看,简单工厂也就是说,有一个生产特定接口实现类的工厂,生产出来的实现类向上转型为它的接口形式。对于拥有这个简单工厂的类来说,它用工厂创建有特定功能的产品对象,从而使用产品的功能。它不关心具体产品的构造过程,这个过程交给简单工厂来做了,就像我们的主函数中调用的那样。
优点:客户端中的用到的产品对象由工厂制造,因此客户端的代码是不用动的。
缺点:工厂类含有必要的判断逻辑,逻辑判断可能会过于复杂,不好维护。所以说,简单工厂的适用环境是当产品对象不多的时候。
//用来生产手机的工厂
public class Facory {
public static Mobile createMobile(String name){
if(name.equals("华为")){
//经过若干道工序后,生产出华为手机
return new HuaWeiMobile();
}
if(name.equals("苹果")){
//也经过若干道工序后,生产出苹果手机
return new AppleMobile();
}
throw new IllegalArgumentException("参数错误");
}
//主函数可以视为实际调用环境
public static void main(String[] args) {
Mobile mobile = Facory.createMobile("华为");
mobile.call();
}
}
//抽象产品,手机
interface Mobile{
void call();
}
//具体产品,华为手机
class HuaWeiMobile implements Mobile{
@Override
public void call() {
//华为手机打电话
}
}
//具体产品,苹果手机
class AppleMobile implements Mobile{
@Override
public void call() {
//苹果手机打电话
}
}
二、工厂方法模式
工厂方法模式克服了简单工厂的一些缺点。简单工厂如果增加一个新的类型的产品,那么就要修改简单工厂中的构造产品的代码,比如这个例子里面要在createMobile方法里面增加代码。这不符合开放闭合原则,修改了以前的代码。工厂方法模式是将工厂也抽象,每一个具体的产品有一个生产它的工厂。
工厂方法模式代码的4个主体:1.抽象工厂 2.具体工厂 3.抽象产品(具有特定功能的产品,将这个功能抽取为接口) 4.具体产品。
//抽象工厂,生产手机的工厂
public abstract class AbstractMobileFacory {
public abstract Mobile createMobile();
}
//具体工厂,生产华为手机的工厂
class HuaWeiMobileFactory extends AbstractMobileFacory{
@Override
public Mobile createMobile() {
return new HuaWeiMobile();
}
}
//具体工厂,生产苹果手机的工厂
class AppleMobileFactory extends AbstractMobileFacory{
@Override
public Mobile createMobile() {
return new AppleMobile();
}
}
//抽象产品,手机,具有抽象功能,打电话
interface Mobile{
void call();
}
//具体产品,华为手机
class HuaWeiMobile implements Mobile{
@Override
public void call() {
//华为手机打电话
}
}
//具体产品,苹果手机
class AppleMobile implements Mobile{
@Override
public void call() {
//苹果手机打电话
}
}
这样的代码符合开放闭合原则,如果现在需求变化,要求新增生产小米手机。那么我们以前写的代码不需要修改,只要添加class XiaoMiMobile 以及XiaoMiMobileFactory即可。这样对于我们重用旧代码,增加新代码,都有好处。工厂方法模式的优势,即“代码复用性”与“扩展性”都体现了出来。
三、优缺点
简单工厂模式的优点:调用者无须关系产品对象的创建过程。 适合产品对象比较少的情况。
简单工厂模式的缺点:将所有产品类的创建都放到了简单工厂里面,如果要添加新的产品类,不得不修改简单工厂,这违反了开放闭合原则(既能进行扩展,又不修改源代码)。
工厂方法模式的优点:符合开放闭合原则,对于新添加的类,它只需要添加新的类和新类的工厂,无须修改已有的类和工厂。代码复用性与系统的扩展性较高。
工厂方法模式的缺点:由于每增加一个产品对象,都要新增产品对象的类及其工厂,增加了代码开发量。
工厂设计模式
简单说:调用环境避免了与“产品对象复杂的构造逻辑”耦合。
刻意练习
(1)简单工厂的基本解释 (1)工厂方法模式的基本解释
(2)简单工厂模式的类图 (2)工厂方法模式的类图
(3)简单工厂的三个主体 (3)工厂方法模式的四个主体
(4)简单工厂的优缺点 (4)工厂方法模式的优缺点
(5)简单工厂的适用环境 (5)工厂方法模式的适用环境
适配器设计模式4
一、定义
将一个已有的接口转换成客户希望的另外一个接口。
我的理解:让一个实现了A接口的类,可以用到接收B接口的方法上,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
下面类图是对象适配与类的适配。对象的适配是构造函数接受对象,类的适配是适配器类直接实现想要的接口。
二、完全解耦
如果某个方法的参数是一个"类"的引用,而不是一个"接口"的引用的话,那么显而易见,我们传递的参数必须是这个类的对象或者是这个类的子类对象。比如下面的抽象类Teacher,在ClassRoom中接收的是一个Teacher类的引用,我们只能传递Teacher类的子类对象。这样做的一点不好之处在于,此方法限制了这个参数必须在Teacher类的继承体系当中的。
public abstract class Teacher {
public abstract void teach();
}
class ClassRoom {
public void beginClass(Teacher teacher) {
teacher.teach();
}
}
如果哪一天,我们请程序员叔叔也来现场演示一下代码,但是我们的程序员叔叔并不是教师。问题就出现了,程序员不能直接应用到ClassRoom的beginClass(Teacher teacher)方法上,因为UncleProgrammer类不是Teacher类的子类。但是如果Teacher是一个接口,那么情况就有所改变。
public interface Teacher {
void teach();
}
interface Coder{
/**
* 演示代码
*/
void showCode();
}
//程序员叔叔类,实现Coder接口
class UncleProgrammer implements Coder{
@Override
public void showCode() {
System.out.println("程序员叔叔演示代码");
}
}
/**
* 教师适配器
* 接收你所拥有的Coder接口实现类,产生你所需要的Teacher接口
*/
class TeacherAdapter implements Teacher{
private Coder coder;
public TeacherAdapter(Coder coder) {
this.coder = coder;
}
@Override
public void teach() {
coder.showCode();
}
}
//客户端使用环境
class ClassRoom {
public void beginClass(Teacher teacher) {
teacher.teach();
}
public static void main(String[] args) {
//有一间教室
ClassRoom classRoom = new ClassRoom();
//有个程序员叔叔
Coder uncle = new UncleProgrammer();
//创建教师适配器,接收你所拥有的接口实现类对象
TeacherAdapter adapter = new TeacherAdapter(uncle);
classRoom.beginClass(adapter);//运行结果:程序员叔叔演示代码
}
}
适配器能够让不同接口之间的方法可以相互使用。使用接口最大的好处在于“完全解耦”,不管是哪些接口,都能做出相关联的适配器,而如果是继承体系的话,就限制死了接收参数要在继承体系中。这也是面向接口编程最大的好处吧。
三、心法总结
适配器模式的终极目标:保留住现有的类,这就体现出适配器模式的代码复用性。
适配器的口诀:接收拥有的接口(实现类),产生需要的接口(实现类)。
适配器模式的优点:
1.提升代码的复用性,能够保留住现有的类,就是代码复用性最好的体现。在我这个例子中,复用了程序员叔叔类的代码。
2.让目标类与被适配的类完全的解耦。从类图也能看出来,目标类只与适配者有接触。
缺点:如果系统里面的适配器比较多,系统会显得很凌乱,不好维护
最经典的适配:Spirng中把Advice通知体系适配成Web体系中的拦截器体系中。
四、使用场景
1.系统需要使用现有的类,但是现有类的接口不符合系统的接口。
2.两个类所做的事情相似,但是有不同的接口。A实现类与B实现类干的活很像,但是方法接收B接口实现类,恰好A实现类干的就是B接口需要做的事情。那么做一个适配器,接收A接口的实现类,并且实现B接口。
3.旧的系统开发的类已经实现了一些功能,但是客户端却只能以另外接口的形式访问,但我们不希望手动更改原有类的时候。
刻意练习
(1)适配器模式的定义
(2)适配器模式的类图
(3)优缺点
(4)使用场景
(5)实例代码
装饰设计模式5
创建一个包装类来动态的扩展一个类的功能。
1.首先看被增强的对象继承或者实现了哪些接口,我们的包装类就继承或者实现这个接口。
2.在包装类中创建一个被增强的对象的引用。
3.使用构造方法来接收这个被增强的对象。
4.重写需要被增强的方法(可以再用一个继承类)。
三、适用条件
被增强的对象,开发人员只能得到它的对象,无法得到它的class文件。比如request、response对象。
另外一个种情况是某个类是final类,不能够被继承,就能够使用包装的方式来组合类。
四、优缺点
优点:通过类的组合,让包装类的功能更强大。也不需要修改被包装类的代码。
缺点:一旦出现BUG,将需要逐级排查,比较麻烦
实例代码:当年我也是包装过Request对象的人,用于增强GET请求中文乱码问题。继承了HttpServlertRequestWrap类。JavaIO框架也大量用了该模式。
刻意练习
(1)装饰者设计模式定义
(2)装饰者设计模式类图
(3)优点
(4)缺点
(5)使用场景
(6)实例代码
建造者设计模式6
一、定义
将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。产品的组成部分是不变的,但每一部分是可以灵活选择的。
三、建造模式心法
使用场景:需要生成的产品对象有复杂的内部结构,通常包含多个成员属性。
四、例子
StringBulider sb = new StringBuilder().append().append().toString();
Header header = new HeaderBuilder()
.setClientId(SOAHeader.SOAP_CLIENT_ID)
.setCorrelationId(SOAHeader.SOAP_CORRELATION_ID)
.buildHeader();
五、优缺点
优点:
1.构造过程清晰明了,代码阅读性很高。
原型模式7
定义
原型模式又名克隆模式,可以理解为【对象的复制】。
二、实现过程
1.实现Cloneable接口。
2.重写clone()方法,将clone()方法的修饰符修改为public,为了让别人包的环境中也能调用此clone()方法。
//细胞
public class Cell implements Cloneable{
//将protected修改为public,并且将返回值修改为Cell类
@Override
public Cell clone() throws CloneNotSupportedException {
Cell cell = (Cell)super.clone();
return cell;
}
public static void main(String[] args) throws Exception {
Cell cell = new Cell();
Cell clonedCell = cell.clone();
System.out.println(cell);//Cell@2d363fb3
System.out.println(clonedCell);//Cell@7d6f77cc
}
}
三、优缺点
复制已有对象的效率很高,因为clone()方法是一个本地方法,因此性能比直接new要好。
很难深克隆,如果引用数据类型的对象没有实现克隆接口,拷贝的实际上是对象的引用地址。
四、浅克隆与深克隆
浅克隆是指,复制一份对象,基本数据类型的值不变,引用数据类型的对象地址给这个克隆对象。
深克隆是指,复制一份对象,对于引用数据类型,重新创建一个新的对象给这个克隆对象。
对于8种基本数据类型而言,浅克隆与深克隆都重新拷贝了一份。如果想要深拷贝一个对象, 这个对象必须要实现Cloneable接口,实现clone方法,并且在clone方法内部,把该对象引用的其他对象也要clone一份 , 这就要求这个被引用的对象必须也要实现Cloneable接口并且实现clone方法。因此,彻底深拷贝,几乎是不可能实现的。因此,基本上所有的克隆都是浅克隆。下面的例子表明,A对象克隆了一份新的,而A对象持有的B对象克隆了它的引用。
public class A implements Cloneable {
private B b = new B();
@Override
public A clone() throws CloneNotSupportedException {
return (A) super.clone();
}
@Override
public String toString() {
return super.toString() + b.toString();
}
public static void main(String[] args) throws CloneNotSupportedException {
A a = new A();
A a2 = a.clone();
System.out.println(a);
System.out.println(a2);
/**
* A@7d6f77cc B@5aaa6d82
* A@73a28541 B@5aaa6d82
*
*/
}
}
class B {
}
刻意练习
(1)一句话描述原型模式
(2)Java提供的原型模式的步骤
(3)优缺点
(4)简述浅克隆与深克隆。一般情况下都是浅克隆
代理设计模式
定义
为目标对象创建一个代理对象,以控制对它的直接访问。
二、分类
静态代理,JDK动态代理,Cglib动态代理。
静态代理本质上就是创建一个类的包装类,所以不用修改被代理的对象。
静态代理缺点是如果被代理的对象加了一个方法,那么代理类也要同步加一个方法。
四、JDK动态代理
优点:JDK动态代理把代理方法的处理都集中到了InvocationHandler这个类的实现中,所以方便统一增加类似于切面的功能,比如记录时间,增加日志、缓存等。
缺点:JDK动态代理只能代理接口的实现
运行时:根据我们在Java代码中的“指示”,动态的生成了相关类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxy implements InvocationHandler{
//被代理的对象的引用,或者说是委托对象,也可以称它为真实对象
private Object target;
public DynamicProxy(Object target) {
this.target = target;
}
public Object getInstance(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前置增强
System.out.println("增强操作,如 日志,安全,事务,缓存");
//调用被代理对象的业务方法
Object result = method.invoke(target,args);
System.out.println("后置增强,用于记录数据,或者记录时间,将数据存到全局map中等");
return result;
}
public static void main(String[] args) {
//业务实现类实现了Operatino接口
Operation subject = new Subject();
//创建动态代理对象
DynamicProxy proxy = new DynamicProxy(subject);
//创建代理实例对象
Operation proxyInstance = (Operation)proxy.getInstance();
//调用代理对象的方法,包裹了真实对象的方法
proxyInstance.function();
}
}
五、Cglib动态代理
如果目标没有实现接口,那么可以用cglib动态代理。cglib是针对类来实现代理的,它生成目标类的一个子类作为代理类。
Cglib动态代理的缺点就是不能对final修饰的类进行代理,final修饰的类不能生成子类。
六、增强工具类例子
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public abstract class DynamicProxy implements InvocationHandler{
//被代理的对象的引用,或者说是委托对象,也很称它为真实对象
private Object target;
public DynamicProxy(Object target) {
this.target = target;
}
//前处理
public abstract Object prehandle();
//后处理
public abstract Object posthandle();
public Object getInstance(){
/**
* 参数1:类加载器
* 参数2:被代理对象所实现的接口,我们动态生成的代理类也将实现这些接口
* 参数3:使用哪个调用处理器的invoke方法,当前就使用这个类的实例对象的invoke方法
*
*/
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
/**
*
* @param proxy 生成的代理对象的引用
* @param method 代理对象调用的方法
* @param args 参数数组
* @return 调用此方法的结果
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
prehandle();
//调用被代理对象的业务方法
Object result = method.invoke(target, args);
posthandle();
return result;
}
}
代理设计模式10知识扩展----注解与AOP
二、注解类知识储备
创建自定义注解和创建一个接口相似,但是注解的interface关键字需要以@符号开头。
每一个注解需要几个元标签。一般来说,有@Retention与@Target。
@Retention指明了这个注解应该被保留的时间。它的取值有三个。
RetentionPolicy.SOURCE 表明注解只在源码阶段保留,在进行编译时它将被丢弃忽视。
RetentionPolicy.CLASS 表明注解只在编译阶段保留,不会被加载到JVM中。
RetentionPolicy.Runtime 表明注解可以保留到运行时,可以通过各种Java反射获取到它。
@Target 指明这个注解应用到哪里。对一个类的剖析,无非是 字段,方法,构造函数,类。它们在java反射包下都有相应的类,比如Class,Method,Filed等。
ElementType.TYPE 这个注解可以用到类,接口,枚举类上
ElementType.METHOD 这个注解可以用到方法上
ElementType.FIELD 这个注解可以用字段上
注解类中的方法不能有参数。
它的返回值可以是基本数据类型,String类型,枚举类型,或者是这些类型的数组。
注解的方法可以有默认值。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)//可以使用反射获取到这个注解
@Target(ElementType.METHOD) //应用的方法上
public @interface MyServiceLog {
long code() default 404L;//状态码,默认是404L错误
String description() default "无法找到网页";//描述
VisitType type();//使用枚举类型,是PC端还是APP端
}
public enum VisitType{
WEB("PC端"),APP("APP端");
//私有的构造函数,外部无法创建此枚举类的实例
VisitType(String type){
method = type;
}
//私有的字段
private String method;
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
}
三、使用反射获取注解的值
首先需要知道的是,所有的注解类的父类都是Annotation这个类。
注解通过反射获取。首先可以通过 Class # isAnnotationPresent() 方法判断这个类是否使用了注解。就像Spring中的@Controller,@Service,@Repository那样。
java.lang.Class
public boolean isAnnotationPresent(Class extends Annotation> annotationClass) {}
如何获取注解对象呢?使用getAnnotation()方法,在java.lang.reflect.Method类中与java.lang.reflect.Field类中均发现了此方法。
public T getAnnotation(Class annotationClass)
只要拿到这个一个类的字节码文件Class对象,就能只要拿到这个类中所有的注解类的对象,就能获取此注解类对象的这些属性,就像下面这样操作。
public class Temp {
@MyServiceLog(code = 200L , description = "响应成功",type = VisitType.WEB)
public void isAuthencated(){
//模拟是否已经被认证
}
public static void main(String[] args) throws NoSuchMethodException {
//先拿到这个类的字节码文件
Class clazz = Temp.class;
//获取到这个类的指定方法
Method method = clazz.getMethod("isAuthencated");
//通过getAnnotatino获取到这个方法上面的注解
MyServiceLog annotation = method.getAnnotation(MyServiceLog.class);
//只要拿到了注解对象,我们就可以为所欲为
//获取状态码
Long code = annotation.code();
//获取描述
String description = annotation.description();
//获取访问方式,先获取到访问方式的枚举,然后获取枚举的值
VisitType type = annotation.type();
String type_str = type.getMethod();
//打印结果
System.out.println("状态码:"+code);
System.out.println("描述:"+description);
System.out.println("访问方式:"+type_str);
/**
* 运行结果:
* 状态码:200
* 描述:响应成功
* 访问方式:PC端
*/
}
}
门面设计模式12
一、定义
门面模式又称为外观模式,该模式把一个模块中的多个类的公共接口封装到一个“统一接口”中,而门面类拥有一个或多个这样的“统一接口”。再简单一点理解就是封装“流程”,简化调用。
为多个复杂的子系统提供一个统一的访问入口
最简单的例子就是一个电脑,封装了CPU、内存、显卡等。电脑视为门面,我们与电脑打交道。
三、优缺点
核心心法:门面设计模式是组合现有的功能。门面模式是负责“组合”子系统已有的功能,用于提供给现外部,满足需要,而不是添加新的实现。
客户端不与各个子系统或者功能模块直接打交道,降低客户端复杂度。
缺点也很明显,当子系统功能修改的时候,门面里面也要修改相关的代码。
五、实际使用
Shiro安全框架的Subject类就是一个非常典型门面类,组合了认证、授权、会话管理等多个复杂子系统。
刻意练习
(1)门面设计模式的定义
(2)门面设计模式核心心法
(3)优点、缺点
(4)实际用过的场景
桥接设计模式13
一、定义
桥接模式属于结构型设计模式,将抽象部分与实现部分分离,使它们都可以独立的变化。
定义:一个抽象类中持有多个接口或抽象类的引用
将两种可以独立变化的抽象分离开来,让它们独立的变化。再通过组合者两个抽象,形成功能强大的组合类
一个抽象类中持有多个接口或抽象类的引用,抽象类可以有不同的子类,接口也可以有不同的实现类,将这些不同的子类与不同的实现类进行组合操作,形成功能强大的组合对象,并且有效的控制了类的数量。
二、 优缺点
两种都具有变化的分类实现独立变化,真的是松耦合的典范
有效的控制类的数量。因为使用了对象的组合,在设计类的过程中,将类的数量由不同维度的“乘积”转为了“和”。
缺点:
需要在设计之处就要面向接口编程,要求程序员拥有较高的面向接口编程的能力,难度可想而知。
四、应用场景思考
多于两个维度或者两个维度以上的变化业务场景,可以考虑使用桥接设计模式。
模版方法模式15
一、定义
抽象模板类定义一个流程,将一些步骤的实现推迟到子类中。
模版方法中的两个角色:
抽象父类:定义模版方法,在模版方法的流程中调用了抽象方法。
具体子类:继承抽象父类,实现抽象方法。
二、优缺点
最大的是代码复用。把公共的方法提取到了父类中,子类只需要实现特定的方法就可以了。
模板方法模式最大的缺点是阅读性差
第二点是模板流程固定,如果要修改流程,那就需要改代码。如果模板定义在JAR包里面,只能重新创建一个新的模板类。
三、心法
当流程固定不变,具体细节可变的时候,可以考虑使用模版方法模式。
如何控制父类的行为
添加钩子方法。在调用抽象方法之前添加钩子方法,钩子方法返回Boolean,子类可以重写这个钩子方法,用于添加复杂的判断条件。
将模版方法定义为final,禁止子类重写。
只有确定流程固定不变的时候才去使用模版方法模式,否则需求变更玩不死你。
刻意练习
(1)模板设计模式的定义(一句话描述)。
(2)模板设计模式优点。
(3)模板设计模式缺点。
(4)类图(脑海过一下)
(5)在设计模板流程中,如何控制父类的行为。
观察者模式16
一、定义
观察者模式又称订阅发布模式。它定义了一种一对多的依赖关系,即一个类向多个类发送消息。多个观察者对象同时观察一个主题对象。当主题对象状态发生变化的时候,主题对象会向所有观察者对象发送消息,使他们更新自己。
Subject:主题角色,或者说是抽象被观察者角色。它定义增加、删除、通知观察者的抽象方法。(定义为接口或者抽象类)
ConcreteSubject:具体的被观察者对象。内部维护一个观察者集合,并实现主题角色的增加、删除、通知观察者等方法。
Observer:抽象观察者。我们可以理解为接收消息的人。它定义一个更新方法,用于接收主题角色发送过来的消息。
ConcreteObserver:具体的观察者。具体的接收消息的人,更新方法一般接收主题角色引用。
三、优缺点
1.降低耦合。主题对象与观察者之间的依赖关系都依赖于抽象,不依赖于具体实现,耦合程度很低。主题对象不知道观察者的具体实现,而观察者只需被动的接收消息,双方的耦合程度非常的低。
缺点:
1.卡顿。如果一个观察者在接收消息后卡顿,剩下的观察者很可能收不到消息。
2.开发代码和调试会比较复杂。当系统中有一个被观察者,多个观察者时,开发代码和调试会比较繁琐复杂。
刻意练习
(1)定义(一句话描述)。
(2)优点。
(3)缺点。
(4)类图(脑海过一下)