设计模式

134 阅读8分钟

课程目标

  • 学习设计模式

  • 掌握设计模式

  • 掌握设计模式相关面试知识

知识要点

  • 工厂模式

  • 不使用工厂模式

  •   public class BMW320 {
          public BMW320() {
              System.out.println("制造-->BMW320");
          }
      }
      
      public class BMW520 {
          public BMW520() {
              System.out.println("制造-->BMW520");
          }
      }
      
      public class Customer {
          public static void main(String[] args) {
              BMW320 bmw320 = new BMW320();
              BMW520 bmw520 = new BMW520();
          }
      }
    
  • 使用简单工厂模式

  •   abstract class BMW { //抽象产品角色:一般是具体产品继承的父类或实现的接口
          public BMW() {
          }
      }
      public class BMW320 extends BMW{
          public BMW320() {
              System.out.println("制造-->BMW320");
          }
      }
      public class BMW520 extends BMW {
          public BMW520() {
              System.out.println("制造-->BMW520");
          }
      }
      
      public class Factory { //工厂所创建的对象就是此角色的实例。
                             //在Java中由一个具体类实现
          public BMW createBMW(int type){
              switch (type){
                  case 320:
                      return new BMW320();
                  case 520:
                      return new BMW520();
                  default:
                      break;
              }
              return null;
          }
      }
      
      public class Customer {
          public static void main(String[] args) {
              Factory factory = new Factory();
              BMW bmw320 = factory.createBMW(320);
              BMW bmw520 = factory.createBMW(520);
      
          }
      }
    
  • 工厂方法模式:工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得塔可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担

  • 开闭原则:当有新的产品产生时,只要按照抽象产品角色、抽象工厂角色提供的合同来生成、那么就可以被客户使用,而不必去修改任何已有的代码

  •   abstract class BMW { 
          public BMW() {
          }
      }
      public class BMW320 extends BMW{
          public BMW320() {
              System.out.println("制造-->BMW320");
          }
      }
      public class BMW520 extends BMW {
          public BMW520() {
              System.out.println("制造-->BMW520");
          }
      }
      
      public interface FactoryBMW {// 抽象工厂模式:这是工厂方法模式的核心,它与应用程序无关。
                                     是具体工厂角色必须实现的接口或者必须继承的父类      
          BMW createBMW();
      }
      public class FactoryBMW320 implements FactoryBMW{
         //具体工厂角色:它含有和具体业务逻辑有关的代码。
           由应用程序调用以创建对应的具体产品的对象
          @Override
          public BMW createBMW() {
              return new BMW320();
          }
      }
      public class FactoryBMW520 implements FactoryBMW{
          @Override
          public BMW createBMW() {
              return new BMW520();
          }
      }
      
      public class Customer {
          public static void main(String[] args) {
              FactoryBMW320 factoryBMW320 = new FactoryBMW320();
              BMW bmw320 = factoryBMW320.createBMW();
      
              FactoryBMW520 factoryBMW520 = new FactoryBMW520();
              BMW bmw520 = factoryBMW520.createBMW();
          }
      }
    
  • 策略模式

  • 定义:定义一组算法,将每一个算法封装起来,从而使它们可以相互切换

  • 特点

  • 一组算法,那就是不同的策略

  • 这组算法都实现了相同的接口或者继承了相同的抽象类,所以可以相互切换

  • 优点:

  • 策略模式提供了管理相关算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承把公共的代码移动到父类里面,从而避免代码重复

  • 使用策略模式可以避免使用多重条件(if-else)语句。多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为混合在一起,统统列在一个多重条件语句里面,比使用继承的办法还要原始和落后

  • 缺点

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。

  • 由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观

  •   public interface Strategy {
          //抽象策略角色:提供接口或抽象类,定义策略组必须要有的方法和属性
          public void algorithmLogic();
      }
      
      public class Context {
          //封装角色,上层访问策略的入口,它持有抽象策略角色的引用
          //并且提供了调用策略的方法
          private Strategy strategy;
      
          public Context(Strategy strategy) {
              this.strategy = strategy;
          }
      
          public void contextInterface(){
              strategy.algorithmLogic();
          }
      }
      
      public class ConcreteStrategyA implements Strategy {
          //实现抽象策略,定义具体的算法逻辑
          @Override
          public void algorithmLogic() {
              //具体的算法逻辑
          }
      }
      
      public class Client {
          public static void main(String[] args) {
              Context context = new Context(new ConcreteStrategyA());
              context.contextInterface();
          }
      }
    
  • 单例设计模式

  • 概念:单例对象必须保证只有一个实例存在

  • 适用场景:单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据连接源

  • 需要频繁实例化然后销毁的对象

  • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象

  • 有状态的工具类对象

  • 频繁访问数据库或文件的对象

  • 懒汉式

  •   public class SingletonOne {
          /**
           * 优点:没有线程安全问题,简单
           * 缺点:提前初始化会延长类加载器加载类的时间;如果不使用会浪费内存空间;不能传递参数
           */
          private static final SingletonOne instance = new SingletonOne();
      
          private SingletonOne() {
          }
      
          public static SingletonOne getInstance(){
              return instance;
          }
      }
    
  • 饿汉式

  •   public class SingletonTwo {
          /**
           * 优点:解决线程安全,延迟初始化
           */
          private SingletonTwo(){
              
          }
          
          public static SingletonTwo getInstance(){
              return Holder.SINGLETON_TWO;
          }
          
          private static class Holder{
              private static final SingletonTwo SINGLETON_TWO = new SingletonTwo();
          }
      }
    
  • 命令模式

  • 背景:当需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,使得请求发送者与请求接收者消解耦

  • 模式定义:将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作

  • 优点:

  • 降低系统的耦合度

  • 新的命令可以很容易地加入到系统中

  • 可以比较容易地设计一个命令队列和宏命令(组合命令)

  • 可以方便地实现对请求的Undo和Redo

  • 缺点: 使用命令模式可能会导致某些系统有过多的命令具体类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用

  •   public class Receiver {//接收者
          public Receiver(){
      
          }
      
          public void actionOne(){
              System.out.println("ActionOne has been taken");
          }
      
          public void actionTwo(){
              System.out.println("ActionTwo has been taken");
          }
      }
      
      public interface Command {//命令
          void execute();
      }
      
      public class ConcreteCommandOne implements Command {//具体命令1
          private Receiver receiver;
      
          public ConcreteCommandOne(Receiver receiver) {
              this.receiver = receiver;
          }
      
          @Override
          public void execute() {
              receiver.actionOne();
          }
      }
      
      public class ConcreteCommandTwo implements Command {//具体命令2
          private Receiver receiver;
      
          public ConcreteCommandTwo(Receiver receiver) {
              this.receiver = receiver;
          }
      
          @Override
          public void execute() {
              receiver.actionTwo();
          }
      }
      
      public class Invoker {//调用程序
          private Command commandOne;
          private Command commandTwo;
      
          public Invoker(Command commandOne, Command commandTwo) {
              this.commandOne = commandOne;
              this.commandTwo = commandTwo;
          }
      
          public void actionOne(){
              commandOne.execute();
          }
      
          public void actionTwo(){
              commandTwo.execute();
          }
      }
      
      public class Client {
          public static void main(String[] args) {
              Receiver receiver = new Receiver();
              ConcreteCommandOne commandOne = new ConcreteCommandOne(receiver);
              ConcreteCommandOne commandTwo = new ConcreteCommandOne(receiver);
              Invoker invoker = new Invoker(commandOne, commandTwo);
              invoker.actionOne();
              invoker.actionTwo();
          }
      
      }
    
  • 代理模式

  • 作用:为其他对象提供一种代理以控制对这个对象的访问

  • 静态代理

  • 优点:可以做到在不修改目标对象的功能前提下,对目标功能进行拓展

  • 缺点:每个代理类都必须实现委托类的接口,如果接口增加方法,则代理类也必须跟着修改。其次,代理类每一个接口对象对应一个委托对象,如果委托对象非常多,则静态代理就非常臃肿,难以胜任

  •   public abstract class Subject {//抽象角色
          public abstract void request();
      }
      public class RealSubject extends Subject {//真实角色
          @Override
          public void request() {
      
          }
      }
      public class ProxySubject extends Subject {
          /*
          静态代理:对具体真实对象直接引用
          代理角色:代理角色需要有对真实角色的引用
          代理做真实角色想做的事情
           */
      
          private RealSubject realSubject = null;
      
          /**
           * 除了代理真实角色该做的事情,代理角色也可以提供附加操作
           */
          @Override
          public void request() {
              preRequest();
      
              if(realSubject == null){
                  realSubject = new RealSubject();
              }
              realSubject.request();
              postRequest();
          }
      
          public void preRequest(){
              //真实角色操作前的附加操作
          }
      
          public void postRequest(){
              //真实角色操作后的附加操作
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              Subject subject = new ProxySubject();
              subject.request();//代理者代替真实者做事情
          }
      }
    
  • jdk动态代理:利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvocationHanlder来处理。只能对对实现了接口的类生成代理只能对实现了接口的类生成代理

  • cglib代理:利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。主要是对指定类生成一个子类,覆盖其中方法实现增强,但是因为采用的是继承,对于final类或方法,是无法继承。

  • 总结:

  • 如果目标对象实现了接口,默认情况下会采用JDK动态代理实现AOP

  • 如果目标对象实现了接口,可以强制使用CGLIB实现AOP

  • 如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换

补充知识点

  • 双重检查锁

  • 两次null 原因

  • 第一个:避免给非空对象加锁,减少性能损耗

  • 第二个:避免多个线程同时加锁

  •   public class Singleton {
          //volatile:指令重排序
          private volatile static Singleton uniqueSingleton;
      
          private Singleton() {
          }
      
          public Singleton getInstance(){
              if(null == uniqueSingleton){
                  synchronized (Singleton.class){
                      if(null == uniqueSingleton){
                          uniqueSingleton = new Singleton();
                      }
                  }
              }
              return uniqueSingleton;
          }
      }