java 面试中常见的设计模式


应是天仙狂醉,乱把白云揉碎


通俗易懂带你了解常见的设计模式

单例模式

单例模式特点

  • 单例类只有一个实例对象;
  • 该单例对象必须由单例类自行创建;
  • 单例类对外提供一个访问该单例的全局访问点。

场景:重量级对象,不需要多个实例,如线程池,数据库连接池,Spring 框架应用中的 ApplicationContext

单例模式的优点

  • 单例模式可以保证内存里只有一个实例,减少了内存的开销。
  • 可以避免对资源的多重占用。
  • 单例模式设置全局访问点,可以优化和共享资源的访问

单例-懒汉模式

懒汉模式:延迟加载,只有在真正使用的时候,才开始实例化。

public class LazySingle {

    private static volatile LazySingle instance; //避免指令重排情况出现

    private LazySingle() {

    }
public static LazySingle getInstance(){
        if (instance == null){
            synchronized (LazySingle.class){   //synchronized定义在里面取代了定义在方法上,避免了不去获取实例也要锁上的情况
                if (instance == null){         //再次判断,防止多线程情况下
                    instance = new LazySingle();
                }
            }
        }
        return instance;
    }

注意:
1)线程安全问题
2)double check 加锁优化
3)指令重排: 在字节码层,按顺序执行

  1. 分配空间
  2. 初始化
  3. 引用赋值

编译器(JIT),CPU有可能对指令进行重排序,导致使用到尚未初始化的实例,可以通过添加volatile关键字进行修饰,防止指令重排。

单例-饿汉模式

饿汉模式:类加载的初始化阶段就完成了实例的初始化。本质上就是借助于jvm类加载机制,保证实例的唯一性。

public class HungrySingleton {

    private static final HungrySingleton instance = new HungrySingleton();

    private HungrySingleton(){

    }

    public static HungrySingleton getInstance(){
        return instance;
    }
    
}

注意:
jvm类加载过程:

  1. 加载二进制数据到内存中,生成对应的Class数据结构
  2. 连接:a.验证 b. 准备(给类的静态成员变量赋默认值) c.解析
  3. 初始化:给类的静态变量赋初值

只有在真正使用对应的类时,才会触发初始化 如(当前类是启动类即main函数所在类,直接进行new操作,访问静态属性,访问静态方法,用反射访问类,初始化一个类的子类等)

单例-静态内部类方式

public class InnerClassSingleton {

    private static class InnerClassHolder{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }

    private InnerClassSingleton(){
    //加上下面这段代码可以防止反射破坏
    //if(instance!=null)
    // {
    // throw new RuntimeException("单例不允许多个实例");
   // }
    }

    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}

静态内部类
1)本质上是利用类的加载机制来保证线程安全
2)只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一种形式

单例-枚举方式

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

枚举方式:能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。

工厂模式

意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
简单来说:用一个方法来代替new关键字

1.先创建一个接口

public interface Shape {
   void draw();
}

2.创建实现接口的实体类

public class Rectangle implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Inside Rectangle::draw() method.");
   }
}
public class Square implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Inside Square::draw() method.");
   }
}

3.创建一个工厂,生成基于给定信息的实体类的对象

public class ShapeFactory {
    
   //使用 getShape 方法获取形状类型的对象
   public Shape getShape(String shapeType){
      if(shapeType == null){
         return null;
      }
      //根据不同的需求获得不同的对象        
      if(shapeType.equalsIgnoreCase("RECTANGLE")){
         return new Rectangle();
      } else if(shapeType.equalsIgnoreCase("SQUARE")){
         return new Square();
      }
      return null;
   }
}

4.使用该工厂,通过传递类型信息来获取实体类的对象

public class FactoryPatternDemo {
 
   public static void main(String[] args) {
      ShapeFactory shapeFactory = new ShapeFactory();
      
      //获取 Rectangle 的对象,并调用它的 draw 方法
      Shape shape1 = shapeFactory.getShape("RECTANGLE");
 
      //调用 Rectangle 的 draw 方法
      shape1.draw();
 
      //获取 Square 的对象,并调用它的 draw 方法
      Shape shape3 = shapeFactory.getShape("SQUARE");
 
      //调用 Square 的 draw 方法
      shape3.draw();
   }
}

场景:作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度

抽象工厂模式

意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类
简单来说:围绕一个超级工厂创建其他工厂

1.为形状创建一个接口

public interface Shape {
   void draw();
}

2.创建实现接口的实体类

public class Rectangle implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Inside Rectangle::draw() method.");
   }
}
public class Square implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Inside Square::draw() method.");
   }
}

3.为颜色创建一个接口

public interface Color {
   void fill();
}

4.创建实现接口的实体类

public class Red implements Color {
 
   @Override
   public void fill() {
      System.out.println("Inside Red::fill() method.");
   }
}
public class Green implements Color {
 
   @Override
   public void fill() {
      System.out.println("Inside Green::fill() method.");
   }
}

5.为 Color 和 Shape 对象创建抽象类来获取工厂

public abstract class AbstractFactory {
   public abstract Color getColor(String color);
   public abstract Shape getShape(String shape);
}

6.创建扩展了 AbstractFactory 的工厂类,基于给定的信息生成实体类的对象

public class ShapeFactory extends AbstractFactory {
    
   @Override
   public Shape getShape(String shapeType){
      if(shapeType == null){
         return null;
      }        
      if(shapeType.equalsIgnoreCase("RECTANGLE")){
         return new Rectangle();
      } else if(shapeType.equalsIgnoreCase("SQUARE")){
         return new Square();
      }
      return null;
   }
   
   @Override
   public Color getColor(String color) {
      return null;
   }
}
public class ColorFactory extends AbstractFactory {
    
   @Override
   public Shape getShape(String shapeType){
      return null;
   }
   
   @Override
   public Color getColor(String color) {
      if(color == null){
         return null;
      }        
      if(color.equalsIgnoreCase("RED")){
         return new Red();
      } else if(color.equalsIgnoreCase("GREEN")){
         return new Green();
      } 
      return null;
   }
}

7.创建一个工厂创造器/生成器类,通过传递形状或颜色信息来获取工厂

public class FactoryProducer {
   public static AbstractFactory getFactory(String choice){
      if(choice.equalsIgnoreCase("SHAPE")){
         return new ShapeFactory();
      } else if(choice.equalsIgnoreCase("COLOR")){
         return new ColorFactory();
      }
      return null;
   }
}

8.使用 FactoryProducer 来获取 AbstractFactory,通过传递类型信息来获取实体类的对象

public class AbstractFactoryPatternDemo {
   public static void main(String[] args) {
 
      //获取形状工厂
      AbstractFactory shapeFactory = FactoryProducer.getFactory("SHAPE");
 
      //获取形状为 Rectangle 的对象
      Shape shape2 = shapeFactory.getShape("RECTANGLE");
 
      //调用 Rectangle 的 draw 方法
      shape2.draw();
      
      //获取形状为 Square 的对象
      Shape shape3 = shapeFactory.getShape("SQUARE");
 
      //调用 Square 的 draw 方法
      shape3.draw();
 
      //获取颜色工厂
      AbstractFactory colorFactory = FactoryProducer.getFactory("COLOR");
 
      //获取颜色为 Red 的对象
      Color color1 = colorFactory.getColor("RED");
 
      //调用 Red 的 fill 方法
      color1.fill();
 
      //获取颜色为 Green 的对象
      Color color2 = colorFactory.getColor("Green");
 
      //调用 Green 的 fill 方法
      color2.fill();
   }
}

在这里插入图片描述

优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象

代理模式

定义:一个类代表另一个类的功能
简单来说:比如windows的快捷方式,你去买火车票可以去代售点买。spring的aop有使用到。

1.创建一个接口
图像类

public interface Image {
   void display();
}

2.创建实现接口的实体类
一个真实类,一个代理类

public class RealImage implements Image {
 
   private String fileName;
 
   public RealImage(String fileName){
      this.fileName = fileName;
      loadFromDisk(fileName);
   }
 
   @Override
   public void display() {
      System.out.println("Displaying " + fileName);
   }
 
   private void loadFromDisk(String fileName){
      System.out.println("Loading " + fileName);
   }
}
public class ProxyImage implements Image{
 
   private RealImage realImage;
   private String fileName;
 
   public ProxyImage(String fileName){
      this.fileName = fileName;
   }
 
   @Override
   public void display() {
      if(realImage == null){
         realImage = new RealImage(fileName); //这里拿到真实类的对象
      }
      realImage.display();
   }
}

3.当被请求时,使用 代理类 来获取 真实类 的对象

public class ProxyPatternDemo {
   
   public static void main(String[] args) {
      Image image = new ProxyImage("test_10mb.jpg");
 
      // 图像将从磁盘加载
      image.display(); 
      System.out.println("");
      // 图像不需要从磁盘加载
      image.display();  
   }
}

注意点:

  1. 和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
  2. 和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

适配器模式

定义:作为两个不兼容的接口之间的桥梁。
意图:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
简单来说:比如美国电器 110V,中国 220V,就要有一个适配器将 110V 转化为 220V。
比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口

1.为媒体播放器和更高级的媒体播放器创建接口
通用媒体播放器就是播放功能

public interface MediaPlayer {
   public void play(String audioType, String fileName);
}

高级媒体播放器能播放 vlc格式和MP4格式

public interface AdvancedMediaPlayer { 
   public void playVlc(String fileName);
   public void playMp4(String fileName);
}

2.创建实现了 高级媒体播放器 接口的实体类
播放vlc的类

public class VlcPlayer implements AdvancedMediaPlayer{
   @Override
   public void playVlc(String fileName) {
      System.out.println("Playing vlc file. Name: "+ fileName);      
   }
 
   @Override
   public void playMp4(String fileName) {
      //什么也不做
   }
}

播放MP4类

public class Mp4Player implements AdvancedMediaPlayer{
 
   @Override
   public void playVlc(String fileName) {
      //什么也不做
   }
 
   @Override
   public void playMp4(String fileName) {
      System.out.println("Playing mp4 file. Name: "+ fileName);      
   }
}

3.创建实现了 通用媒体播放器 接口的适配器类

代理模式和适配器模式区别:

看到这里你会觉得和代理模式很像,但其实是不一样的。
首先,你要知道一句话:只需要将原接口转化为客户希望的另一个接口,就是适配器模式
代理类模式是与原对象实现同一个接口,适配器类模式是匹配一个新接口。
适配器类会去拿目标接口的对象,代理类不会,这里就去拿了我们需要的高级媒体播放功能的接口对象。

public class MediaAdapter implements MediaPlayer {
 
   AdvancedMediaPlayer advancedMusicPlayer;  //适配器类会去拿目标类的对象,代理类不会
 
   public MediaAdapter(String audioType){
      if(audioType.equalsIgnoreCase("vlc") ){
         advancedMusicPlayer = new VlcPlayer();       
      } else if (audioType.equalsIgnoreCase("mp4")){
         advancedMusicPlayer = new Mp4Player();
      }  
   }
 
   @Override
   public void play(String audioType, String fileName) {
      if(audioType.equalsIgnoreCase("vlc")){
         advancedMusicPlayer.playVlc(fileName);
      }else if(audioType.equalsIgnoreCase("mp4")){
         advancedMusicPlayer.playMp4(fileName);
      }
   }
}

4.创建实现了 通用媒体播放器 接口的实体类。
你需要播放高级的功能,好,我们给你一个适配器类,通过适配器就可以播放更多格式的了。

public class AudioPlayer implements MediaPlayer {
   MediaAdapter mediaAdapter; 
 
   @Override
   public void play(String audioType, String fileName) {    
 
      //播放 mp3 音乐文件的内置支持
      if(audioType.equalsIgnoreCase("mp3")){
         System.out.println("Playing mp3 file. Name: "+ fileName);         
      } 
      //mediaAdapter 提供了播放其他文件格式的支持
      else if(audioType.equalsIgnoreCase("vlc") 
         || audioType.equalsIgnoreCase("mp4")){
         mediaAdapter = new MediaAdapter(audioType);
         mediaAdapter.play(audioType, fileName);
      }
      else{
         System.out.println("Invalid media. "+
            audioType + " format not supported");
      }
   }   
}

5.测试
使用 AudioPlayer 来播放不同类型的音频格式

public class AdapterPatternDemo {
   public static void main(String[] args) {
      AudioPlayer audioPlayer = new AudioPlayer();
 
      audioPlayer.play("mp3", "beyond the horizon.mp3");
      audioPlayer.play("mp4", "alone.mp4");
      audioPlayer.play("vlc", "far far away.vlc");
      audioPlayer.play("avi", "mind me.avi");
   }
}

在这里插入图片描述

注意点:适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。

策略模式

意图:意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
简单来说:旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。

1.创建一个接口
策略接口

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

2.创建实现接口的实体类
加法操作类

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;
   }
}

乘法操作类

public class OperationMultiply implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 * num2;
   }
}

3.创建 环境类。
策略模式的关键:在里面引用了策略接口的对象,通过构造方法传入策略,可以实现不同的功能

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);
   }
}

4.在new的Context中传入不同的策略,会执行不同的操作

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));
 
      context = new Context(new OperationMultiply());    
      System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
   }
}

使用场景:

  1. 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
  2. 一个系统需要动态地在几种算法中选择一种。
  3. 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

观察者模式

意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新

简单来说:拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价
一般来说,观察者模式有 主题,观察者,客户端几部分即Subject、Observer 和 Client
1.创建 主题(Subject )类
首先定义一个观察者数组,并实现增、删及通知操作。它的职责很简单,就是定义谁能观察,谁不能观察,用Vector是线程同步的,比较安全,也可以使用ArrayList,是线程异步的,但不安全。

  1. 容器:定义一个观察者放到一个List或其他集合容器里面
  2. 添加:把观察者添加到容器
  3. 移除:把观察者移除出容器
  4. 通知:通知观察者有数据改变
import java.util.ArrayList;
import java.util.List;
 
public class Subject {
   
   private List<Observer> observers 
      = new ArrayList<Observer>();
 
   public void add(Observer observer){
      observers.add(observer);      
   }
    public void remove(Observer observer){
      observers.remove(observer);      
   }
 
   public void notifyAllObservers(){
      for (Observer observer : observers) {
         observer.update();
      }
   }  
}

2.创建 观察者 ( Observer )类。
观察者一般是一个接口,每一个实现该接口的实现类都是具体观察者。

public interface Observer {
     //更新
    public void update();
 }

3.创建实体观察者类。

public class ConcreteObserver implements Observer {
 
     @Override
     public void update() {
        System.out.println("收到消息,进行处理");
    }

}

4.实现具体主题
继承Subject类,在这里实现具体业务,在具体项目中,该类会有很多变种。

 public class ConcreteSubject extends Subject {

    //具体业务
    public void doSomething() {
       //...
         super.notifyObserver();
    }
    }

5.Client客户端
首先创建一个被观察者,然后定义一个观察者,将该被观察者添加到该观察者的观察者数组中,进行测试。

public class Client {
     
      public static void main(String[] args) {
         //创建一个主题
         ConcreteSubject subject = new ConcreteSubject();
          //定义一个观察者
          Observer observer = new ConcreteObserver();
          //观察
          subject.addObserver(observer);
         //开始活动
         subject.doSomething();
     }
     
 }

注意点:

  1. JAVA 中已经有了对观察者模式的支持类。
  2. 避免循环引用。
  3. 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。

模板模式

意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
简单来说:父类定义流程,子类实现流程。
spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存

这个模式比较容易理解,我们直接看例子
1.创建一个抽象类,它的模板方法被设置为 final:防止恶意操作
里面的模板方法其实就是定义了一套玩游戏的流程

public abstract class Game {
   abstract void initialize();
   abstract void startPlay();
   abstract void endPlay();
 
   //模板
   public final void play(){
 
      //初始化游戏
      initialize();
 
      //开始游戏
      startPlay();
 
      //结束游戏
      endPlay();
   }
}

2.创建扩展了上述类的实体类
定义一个板球游戏和足球游戏,继承模板类,他们有自己的游戏逻辑。

public class Cricket extends Game {
 
   @Override
   void endPlay() {
      System.out.println("Cricket Game Finished!");
   }
 
   @Override
   void initialize() {
      System.out.println("Cricket Game Initialized! Start playing.");
   }
 
   @Override
   void startPlay() {
      System.out.println("Cricket Game Started. Enjoy the game!");
   }
}
public class Football extends Game {
 
   @Override
   void endPlay() {
      System.out.println("Football Game Finished!");
   }
 
   @Override
   void initialize() {
      System.out.println("Football Game Initialized! Start playing.");
   }
 
   @Override
   void startPlay() {
      System.out.println("Football Game Started. Enjoy the game!");
   }
}

3.使用 Game 的模板方法 play() 来演示游戏的定义方式。

public class TemplatePatternDemo {
   public static void main(String[] args) {
 
      Game game = new Cricket();
      game.play();
      System.out.println();
      game = new Football();
      game.play();      
   }
}

使用场景

  1. 有多个子类共有的方法,且逻辑相同。
  2. 重要的、复杂的方法,可以考虑作为模板方法

注意点:
为防止恶意操作,一般模板方法都加上 final 关键词

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值