从头开始学设计模式

单例设计模式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

一、基本定义

“针对”一组算法,“将”每一个算法封装到具有相同接口的独立的类中,“从而”使它们可以相互替换。
核心:策略实现相同接口,可以相互替换。即封装的各个算法地位平等,它们具有相同的接口,可以相互进行替换。


image.png

二、速记

策略设计模式的重点不是如何实现算法,而是如何组织、调用这些算法。策略设计模式让程序的结构更加灵活,让系统具有更好的灵活性和扩展性,降低需求变动的维护成本。
核心:封装地位相同的各种算法,让它们可以动态的切换。
优点:扩展性很好,增加算法只需要增加一个新的策略实现。
缺点:随着策略越来越多,维护这些的策略也很困难。
场景:用于计算公式经常变动的业务场景,比如学生的学分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.具体产品


image.png

从上述代码来看,简单工厂也就是说,有一个生产特定接口实现类的工厂,生产出来的实现类向上转型为它的接口形式。对于拥有这个简单工厂的类来说,它用工厂创建有特定功能的产品对象,从而使用产品的功能。它不关心具体产品的构造过程,这个过程交给简单工厂来做了,就像我们的主函数中调用的那样。
优点:客户端中的用到的产品对象由工厂制造,因此客户端的代码是不用动的。
缺点:工厂类含有必要的判断逻辑,逻辑判断可能会过于复杂,不好维护。所以说,简单工厂的适用环境是当产品对象不多的时候。

//用来生产手机的工厂
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() {
        //苹果手机打电话
    }
}

二、工厂方法模式

image.png

工厂方法模式克服了简单工厂的一些缺点。简单工厂如果增加一个新的类型的产品,那么就要修改简单工厂中的构造产品的代码,比如这个例子里面要在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接口的方法上,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
下面类图是对象适配与类的适配。对象的适配是构造函数接受对象,类的适配是适配器类直接实现想要的接口。


image.png

image.png

二、完全解耦

如果某个方法的参数是一个"类"的引用,而不是一个"接口"的引用的话,那么显而易见,我们传递的参数必须是这个类的对象或者是这个类的子类对象。比如下面的抽象类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 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、内存、显卡等。电脑视为门面,我们与电脑打交道。


image.png

三、优缺点

核心心法:门面设计模式是组合现有的功能。门面模式是负责“组合”子系统已有的功能,用于提供给现外部,满足需要,而不是添加新的实现。
客户端不与各个子系统或者功能模块直接打交道,降低客户端复杂度。
缺点也很明显,当子系统功能修改的时候,门面里面也要修改相关的代码。

五、实际使用

Shiro安全框架的Subject类就是一个非常典型门面类,组合了认证、授权、会话管理等多个复杂子系统。

刻意练习
(1)门面设计模式的定义
(2)门面设计模式核心心法
(3)优点、缺点
(4)实际用过的场景

桥接设计模式13

一、定义

桥接模式属于结构型设计模式,将抽象部分与实现部分分离,使它们都可以独立的变化。
定义:一个抽象类中持有多个接口或抽象类的引用
将两种可以独立变化的抽象分离开来,让它们独立的变化。再通过组合者两个抽象,形成功能强大的组合类
一个抽象类中持有多个接口或抽象类的引用,抽象类可以有不同的子类,接口也可以有不同的实现类,将这些不同的子类与不同的实现类进行组合操作,形成功能强大的组合对象,并且有效的控制了类的数量。


二、 优缺点

两种都具有变化的分类实现独立变化,真的是松耦合的典范
有效的控制类的数量。因为使用了对象的组合,在设计类的过程中,将类的数量由不同维度的“乘积”转为了“和”。
缺点:
需要在设计之处就要面向接口编程,要求程序员拥有较高的面向接口编程的能力,难度可想而知。

四、应用场景思考

多于两个维度或者两个维度以上的变化业务场景,可以考虑使用桥接设计模式。

模版方法模式15

一、定义

抽象模板类定义一个流程,将一些步骤的实现推迟到子类中。
模版方法中的两个角色:
抽象父类:定义模版方法,在模版方法的流程中调用了抽象方法。
具体子类:继承抽象父类,实现抽象方法。


image.png

二、优缺点

最大的是代码复用。把公共的方法提取到了父类中,子类只需要实现特定的方法就可以了。
模板方法模式最大的缺点是阅读性差
第二点是模板流程固定,如果要修改流程,那就需要改代码。如果模板定义在JAR包里面,只能重新创建一个新的模板类。

三、心法

当流程固定不变,具体细节可变的时候,可以考虑使用模版方法模式。
如何控制父类的行为
添加钩子方法。在调用抽象方法之前添加钩子方法,钩子方法返回Boolean,子类可以重写这个钩子方法,用于添加复杂的判断条件。
将模版方法定义为final,禁止子类重写。
只有确定流程固定不变的时候才去使用模版方法模式,否则需求变更玩不死你。

刻意练习
(1)模板设计模式的定义(一句话描述)。
(2)模板设计模式优点。
(3)模板设计模式缺点。
(4)类图(脑海过一下)
(5)在设计模板流程中,如何控制父类的行为。

观察者模式16

一、定义

观察者模式又称订阅发布模式。它定义了一种一对多的依赖关系,即一个类向多个类发送消息。多个观察者对象同时观察一个主题对象。当主题对象状态发生变化的时候,主题对象会向所有观察者对象发送消息,使他们更新自己。


image.png

Subject:主题角色,或者说是抽象被观察者角色。它定义增加、删除、通知观察者的抽象方法。(定义为接口或者抽象类)
ConcreteSubject:具体的被观察者对象。内部维护一个观察者集合,并实现主题角色的增加、删除、通知观察者等方法。
Observer:抽象观察者。我们可以理解为接收消息的人。它定义一个更新方法,用于接收主题角色发送过来的消息。
ConcreteObserver:具体的观察者。具体的接收消息的人,更新方法一般接收主题角色引用。

三、优缺点

1.降低耦合。主题对象与观察者之间的依赖关系都依赖于抽象,不依赖于具体实现,耦合程度很低。主题对象不知道观察者的具体实现,而观察者只需被动的接收消息,双方的耦合程度非常的低。
缺点:
1.卡顿。如果一个观察者在接收消息后卡顿,剩下的观察者很可能收不到消息。
2.开发代码和调试会比较复杂。当系统中有一个被观察者,多个观察者时,开发代码和调试会比较繁琐复杂。

刻意练习
(1)定义(一句话描述)。
(2)优点。
(3)缺点。
(4)类图(脑海过一下)

你可能感兴趣的:(从头开始学设计模式)