设计模式(补档)


软件设计原则

常⽤的⾯向对象设计原则包括7个,这些原则并不是孤⽴存在的,它们相互 依赖,相互补充。

  • 开闭原则(Open Closed Principle,OCP)
  • 单⼀职责原则(Single Responsibility Principle, SRP)
  • ⾥⽒替换原则(Liskov Substitution Principle,LSP)
  • 依赖倒置原则(Dependency Inversion Principle,DIP)
  • 接⼝隔离原则(Interface Segregation Principle,ISP)
  • 合成/聚合复⽤原则(Composite/Aggregate Reuse Principle, C/ARP)
  • 最少知识原则(Least Knowledge Principle,LKP)
  • 或者迪⽶特法则 (Law of Demeter,LOD

什么是设计模式

设计模式是软件开发⼈员在软件开发过程中⾯临的⼀般问题的解决⽅案。这些解决⽅案是众多软件开发⼈员经过相当⻓的 ⼀段时间的试验和错误总结出来的。

设计模式是⼀套被反复使⽤的、多数⼈知晓的、经过分类编⽬的、代码设计经验的总结。使⽤设计模式是为了重⽤代码、让代码更容易被他⼈理解、保证代码可靠性。

项⽬中合理地运⽤设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了 ⼀个在我们周围不断重复发⽣的问题,以及该问题的核⼼解决⽅案

设计模式的分类

创建型: 在创建对象的同时隐藏创建逻辑,不使⽤ new 直接实例化对象,程序在判断需要创建哪些对象时更灵活。包括⼯⼚/抽象⼯⼚/单例/ 建造者/原型模式。

结构型: 通过类和接⼝间的继承和引⽤实现创建复杂结构的对象。包 括适配器/桥接模式/过滤器/组合/装饰器/外观/享元/代理模式。

⾏为型: 通过类之间不同通信⽅式实现不同⾏为。包括责任链/命名/解 释器/迭代器/中介者/备忘录/观察者/状态/策略/模板/访问者模式

简单⼯⼚模式

简单⼯⼚模式指由⼀个⼯⼚对象来创建实例,客户端不需要关注创建逻辑,只需提供传⼊⼯⼚的参数。

适⽤于⼯⼚类负责创建对象较少的情况,缺点是如果要增加新产品,就需 要修改⼯⼚类的判断逻辑,违背开闭原则,且产品多的话会使⼯⼚类⽐较 复杂。

抽象产品

public interface Phone {
    void getBrand();
}

具体产品

public class Meizu implements Phone {
    @Override
    public void getBrand() {
        System.out.println("魅族");
    }
}
public class Xiaomi implements Phone {
    @Override
    public void getBrand() {
        System.out.println("小米");
    }
}

工厂类

public class PhoneFactory{
    public static Phone getPhone(String phone){
        if("小米".equals(phone)){
           return new Xiaomi();
        }else if ("魅族".equals(phone)){
          return new Meizu();
        }else {
            return null;
        }
    }
}

消费者

public class Customer {
    public static void main(String[] args) {
        PhoneFactory.getPhone("Xiaomi").getBrand();
        PhoneFactory.getPhone("Meizu").getBrand();
    }
}

⼯⼚⽅法模式

和简单⼯⼚模式中⼯⼚负责⽣产所有产品相⽐,⼯⼚⽅法模式将⽣成具体 产品的任务分发给具体的产品⼯⼚。

也就是定义⼀个抽象⼯⼚,其定义了产品的⽣产接⼝,但不负责具体的产 品,将⽣产任务交给不同的派⽣类⼯⼚。这样不⽤通过指定类型来创建对 象了。

在简单工厂模式中,在抽象工厂下分为具体工厂

public class XiaomiFactory implements PhoneFactory {
    @Override
    public Phone getPhone() {
        return new Xiaomi();
    }
}
public class MeizuFactory implements PhoneFactory{
    @Override
    public Phone getPhone() {
        return new Meizu();
    }
}

消费者

public class Customer {
    public static void main(String[] args) {
        Phone xiaomi = new XiaomiFactory().getPhone();
        Phone meizu = new MeizuFactory().getPhone();
        xiaomi.getBrand();
        meizu.getBrand();
    }
}

抽象⼯⼚模式

简单⼯⼚模式和⼯⼚⽅法模式不管⼯⼚怎么拆分抽象,都只是针对⼀类产品,如果要⽣成另⼀种产品,就⽐较难办了

抽象⼯⼚模式通过在 AbstarctFactory 中增加创建产品的接⼝,并在具体⼦⼯⼚中实现新加产品的创建。

抽象工厂仅仅是在工厂方法模式下新增了一些接口,只是工厂模式的一个拓展,当抽象工厂模式只有一个产品体系的话就会退化成工厂模式。

单例模式

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。

Spring 中的 Bean 默认都是单例的,作用域是singleton

懒汉式,线程不安全

用private声明了构造方法,这样做其他类就不能直接通过new实例化了

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

懒汉式,线程安全

synchronized修饰get()方法

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

饿汉式

用private声明了构造方法,这样做其他类就不能直接通过new实例化了
(类加载时就初始化)这样就会浪费了内存空间。

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
	    return instance;  
    }  
}

双重校验锁

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
	    if (singleton == null) {  
	        synchronized (Singleton.class) {  
	            if (singleton == null) {  
	                singleton = new Singleton();  
	            }  
	        }  
	    }  
	    return singleton;  
	}  
}

为什么加上volatile关键字,有什么用呢?

new的时候不是原子性, singleton = new Singleton(); 这段代码其实是分为三步执行:

  1. 分配内存空间
  2. 执行构造方法,初始化对象
  3. 将引用指向分配的内存地址

但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。

指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getSingleton() 后发现 singleton 不为空,因此返回 singleton,但此时 singleton 还未被初始化。

双重检查加锁单例模式为什么两次校验?

第一次校验

为了代码提高代码执行效率:由于单例模式只要一次创建实例即可,所以当创建了一个实例之后,再次调用getInstance方法就不必要进入同步代码块,不用竞争锁。直接返回前面创建的实例即可。

第二次校验

防止二次创建实例:假如有一种情况,当singleton还未被创建时,线程t1调用getInstance方法,由于第一次判断singleton == null,此时线程t1准备继续执行,但是由于CPU资源被线程t2抢占了,此时t2页调用getInstance方法,同样的,由于singleton并没有实例化,t2同样可以通过第一个if,然后继续往下执行,同步代码块,第二个if也通过,然后t2线程创建了一个实例singleton。此时t2线程完成任务,资源又回到t1线程,t1此时也进入同步代码块,如果没有这个第二个if,那么,t1就也会创建一个singleton实例,那么,就会出现创建多个实例的情况,但是加上第二个if,就可以完全避免这个多线程导致多次创建实例的问题。

代理模式

代理模式的本质就是将非核心事务转交给第三方处理,比如老板和秘书,明星和经纪人

代理模式的本质是⼀个中间件,主要⽬的是解耦合服务提供者和使⽤者。使⽤者通过代理间接的访问服务提供者,便于后者的封装和控制。

优点:

  • 高内聚低耦合:核心代码专注业务本身,把非业务代码通通交给代理实现
  • 增加扩展性:扩展其他功能时不影响核心业务代码

静态代理

静态代理是在程序运行前,代理类的.class文件就已经存在了

定义接口

//定义一个明星的接口
public interface IStarDao {

    void action();
}

实现行为

public class StarDao implements IStarDao {
    @Override
    public void action() {
        System.out.println("拍广告");
    }
}

定义代理类,静态代理的关键代码

经纪人代理

public class AgentProxy implements IStarDao {

    private IStarDao action;

    public AgentProxy(IStarDao target) {
        this.action = target;
    }

    @Override
    public void action() {
        System.out.println("经纪人接广告");
        //表演行动开始::代理人是经纪人,但实际赚钱的是明星
        perform.action();
        System.out.println("经纪人数钱");
    }
}

实现

public class Client {
    public static void main(String[] args) {
        
        //明星
        StarDao starDao = new StarDao();
        
        //帮明星代理的经纪人
        AgentProxy agentProxy = new AgentProxy(starDao);
        
        //帮明星代理的经纪人,让明星开始他的表演
        agentProxy.action();
    }
}

优点:拓展新功能时能不修改我们核心代码。

缺点:代理对象和目标对象类需要实现一样的接口,会导致代理类会非常非常多。并且重用性不强

动态代理

JDK动态代理

JDK代理实现是利用lang包下的reflect

java.lang.reflect

定义一个明星的接口

public interface IStarDao {
    void action();
}

实现明星的行为

public class StarDao implements IStarDao {
    @Override
    public void action() {
        System.out.println("拍广告");
    }
}

JDK动态代理的关键代码

代理类无需再和目标对象类实现同样的接口,更加的灵活。

代理实例的三个参数:

  • ClassLoader loader : 指定当前目标对象使用的类加载器
  • Class< ? >[] interfaces : 目标对象实现的接口类型
  • InvocationHandler : 事情处理,执行目标对象的方法时,会触发事情处理方法,把当前执行的目标对象方法作为参数传入
public class AgentProxyFactory {

    private Object target;

    public AgentProxyFactory(Object target){
        this.target = target;
    }

    /**
     * 给目标对象 生成一个代理对象
     */
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //反射机制调用目标对象的方法
                System.out.println("经纪人接广告");
                Object returnValue = method.invoke(target,args);
                System.out.println("经纪人数钱");
                return returnValue;
            }

        });
    }
}

可以发现代理类不需要继承接口,但需要在真正实例化时将目标对象给代理对象既可。

public class Client {
    public static void main(String[] args) {

        //创建目标对象
        StarDao iStarDao = new StarDao();

        //给目标对象,创建代理对象,可以转成Dao
        IStarDao proxyInstance = (IStarDao) new AgentProxyFactory(iStarDao).getProxyInstance();

        //通过代理对象,调用目标对象
        proxyInstance.action();
    }
}

优势:

  • 在扩展功能时不修改核心代码,
  • JDK动态代理类不需要和目标对象类一样都需要实现同样的接口,让代理类更加灵活。

劣势:目标对象依然需要实现接口。

cglib动态代理

cglib代理也称 子类代理,它是从内存中构建出一个子类来扩展目标对象的功能。

定义目标类,不同的不需要再定义接口,直接实现。

public class StarDao  {
    public void action() {
        System.out.println("拍广告");
    }
}

代理类AgentProxyFactory 实现 MethodInterceptor 接口用来调用目标类,实现动态代理

public class AgentProxyFactory implements MethodInterceptor {

    private Object targer;

    public AgentProxyFactory(Object targer) {
        this.targer = targer;
    }

    //返回一个代理对象,是target 对象的代理对象
    public Object getProxyInstance(){
        //1.创建一个工具类
        Enhancer enhancer = new Enhancer();
        //2.设置父类
        enhancer.setSuperclass(targer.getClass());
        //3.设置回调函数
        enhancer.setCallback(this);
        //4.创建子类对象,即代理对象
        return enhancer.create();
    }

	//调用目标对象
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

        System.out.println("经纪人接广告");
        Object returnValue = method.invoke(targer, args);
        System.out.println("经纪人数钱");
        return returnValue;
    }
}
public class Client {
    public static void main(String[] args) {
        //创建目标对象
        StarDao target = new StarDao();
        //获取到代理对象,并且将目标对象传给代理对象
        StarDao proxyInstance = (StarDao) new AgentProxyFactory(target).getProxyInstance();
        //执行代理对象的方法
        proxyInstance.sing();
    }
}

优势:不用实现同一个接口。让代理模式真正灵动起来。

劣势:需要多导入jar包了

静态代理和动态代理的区别

灵活性 :动态代理更加灵活,不需要必须实现接⼝,可以直接代理实现类,并且可以不需要针对每个⽬标类都创建⼀个代理类。静态代理中,接⼝⼀旦新增加⽅法,⽬标对象和代理对象都要进⾏修改,⾮常麻烦

JVM 层⾯ :静态代理在编译时就将接⼝、实现类、代理类这些都变成了⼀个个实际的 class ⽂件。⽽动态代理是在运⾏时动态⽣成类字节码,并加载到 JVM 中的。

观察者模式

观察者模式主要⽤于处理对象间的⼀对多的关系,是⼀种对象⾏为模式。该模式的实际应⽤场景⽐较容易确认,当⼀个对象状态发⽣变化时,所有该对象的关注者均能收到状态变化通知,以进⾏相应的处理。

适配器模式

将两个不同接⼝的类来进⾏通信,在不修改这两个的前提下可能会需要某个中间件来完成这个衔接的过程。这个中间件就是适配器。

所谓适配器模式就是将⼀个类的接⼝,转换成客户期望的另⼀个接⼝。它可以让原本两个不兼容的接⼝能够⽆缝完成对接。作为中间件的适配器将⽬标类和适配者解耦,增加了类的透明性和可复⽤性。

策略模式

策略模式定义了一系列算法或策略,并将每个算法封装在独立的类中,使得它们可以互相替换。通过使用策略模式,可以在运行时根据需要选择不同的算法,而不需要修改客户端代码。

主要解决:在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护。

  • 环境(Context):持有一个策略类的引用,最终给客户端调用。
  • 抽象策略(Abstract Strategy):定义了策略对象的公共接口或抽象类,规定了具体策略类必须实现的方法。
  • 具体策略(Concrete Strategy):实现了抽象策略定义的接口或抽象类

抽象策略(Abstract Strategy)

public interface Strategy {
   public int doOperation(int num1, int num2);
}

具体策略(Concrete Strategy)

public class OperationAdd implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 + num2;
   }
}
public class OperationSubtract implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 - num2;
   }
}

环境(Context)

public class Context {
   private Strategy strategy;
 
   public Context(Strategy strategy){
      this.strategy = strategy;
   }
 
   public int executeStrategy(int num1, int num2){
      return strategy.doOperation(num1, num2);
   }
}

demo

public class StrategyPatternDemo {
   public static void main(String[] args) {
      Context context = new Context(new OperationAdd());    
      System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
 
      context = new Context(new OperationSubtract());      
      System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
      
   }
}

文章作者: Aiaa
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Aiaa !
  目录