一篇文章带你彻底了解Java常用的设计模式

不念不忘少年蓝@ 2024-05-03 15:05 151阅读 0赞

文章目录

    • 前言
      1. 工厂模式
      • 使用示例代码
      • 优势
      1. 单例模式
      • 说明
      • 使用示例代码
      • 优势
      1. 原型模式
      • 使用示例代码
      • 优势
      1. 适配器模式
      • 使用示例代码
      • 优势
      1. 观察者模式
      • 使用示例代码
      • 优势
      1. 策略模式
      • 使用示例代码
      • 优势
      1. 装饰者模式
      • 使用示例代码
      • 优势
      1. 模板方法模式
      • 使用示例代码
      • 优势
    • 总结

前言

说到Java开发,设计模式肯定是绕不开的,Java常用的设计模式主要包括很多,一种说是有10种设计模式,还有一种说是23种设计模式。我这边重点解释了开发中常用到的8种设计模式,分别是:工厂模式、单例模式、原型模式、适配器模式、观察者模式、策略模式、装饰者模式、模板方法模式。其中每种模式都有其特定的应用场景和优缺点,在实际开发中,根据具体需求选择适合的设计模式可以提高代码的可维护性、可扩展性和重用性。需要根据具体情况来合理选择和组合使用设计模式,最终的目的就是让我们的代码优雅且可读性强,整体逻辑高内聚、低耦合。

?工厂模式(Factory):通过工厂类来创建对象,隐藏对象的创建细节,提供对象的接口给客户端使用
?单例模式(Singleton):确保一个类只有一个实例,并提供全局访问点
?原型模式(Prototype):通过复制现有对象来创建新对象,避免了使用new关键字创建对象
?适配器模式(Adapter):将一个类的接口转换成客户端所期望的另一个接口
?观察者模式(Observer):定义了一种一对多的依赖关系,当一个对象状态发生改变时,其相关依赖对象都会得到通知和更新
?策略模式(Strategy):定义了一系列可互换的算法,并使得算法的变化独立于使用算法的客户端
?装饰者模式(Decorator):在不改变原始对象的基础上,动态地给对象添加额外的功能
?模板方法模式(Template Method):定义一个算法骨架,将一些步骤延迟到子类中实现

1. 工厂模式

Java使用工厂模式是为了实现对象的创建和使用的分离,提供一种灵活的方式来创建对象实例。工厂模式提供一种可扩展、灵活、封装对象创建逻辑的方式,使代码更易于维护、测试和修改。它帮助实现了软件设计中的开闭原则,即对扩展开放、对修改关闭。

使用示例代码

定义产品接口,并定义产品操作的抽象方法

  1. /**
  2. * 产品接口
  3. */
  4. public interface Product {
  5. void operation();
  6. }

产品A实现产品接口,并重写operation()方法

  1. /**
  2. * 产品A类
  3. */
  4. public class ProductA implements Product {
  5. @Override
  6. public void operation() {
  7. System.out.println("产品A的操作!");
  8. }
  9. }

产品B实现产品接口,并重写operation()方法

  1. /**
  2. * 产品B类
  3. */
  4. public class ProductB implements Product {
  5. @Override
  6. public void operation() {
  7. System.out.println("产品B的操作!");
  8. }
  9. }

建立工厂类,用于产品的生产

  1. /**
  2. * 工厂类
  3. */
  4. public class Factory {
  5. public Product createProduct(String type){
  6. if ("A".equals(type)) {
  7. return new ProductA();
  8. } else if ("B".equals(type)) {
  9. return new ProductB();
  10. } else {
  11. throw new IllegalArgumentException("无法创建指定类型的产品");
  12. }
  13. }
  14. }

使用工厂对象来创建产品A和产品B,通过传递不同的参数”A”和”B”来指定要创建的产品类型。然后调用产品的operation()方法来执行具体产品的操作,通过工厂类来创建产品对象,客户端代码就不需要直接依赖于具体产品类,而只需要依赖于产品接口

  1. public class Test {
  2. public static void main(String[] args) {
  3. Factory factory = new Factory();
  4. Product productA = factory.createProduct("A");
  5. productA.operation();
  6. Product productB = factory.createProduct("B");
  7. productB.operation();
  8. Product productC = factory.createProduct("C");
  9. productC.operation();
  10. }
  11. }

运行结果
在这里插入图片描述

优势

  1. 隐藏具体实现类
  2. 简化对象的创建过程
  3. 降低系统的耦合度
  4. 支持扩展和灵活性

2. 单例模式

Java使用单例模式是为了确保一个类只能创建一个对象实例,并提供全局访问点。可以限制一个类只能创建一个对象实例,提供了一个全局访问点,使得其他对象可以方便地获取到该单例对象并使用它。这样可以简化代码,减少对全局变量的使用,同时也方便对单例对象进行集中管理和控制。由于单例模式只创建一个对象实例,节省了系统资源和内存空间。在需要频繁创建和销毁对象的场景中,使用单例模式可以提高性能和效率。

说明

  1. 将构造方法设置为私有,以防止外部通过new关键字创建新的实例
  2. 声明一个私有静态变量instance,用于存储单例的唯一实例
  3. 提供一个公共的静态方法getInstance(),用于获取单例实例

使用示例代码

懒汉式单例模式,存在线程安全性问题,当多个线程同时调用getInstance()方法时,可能会创建多个实例

  1. /**
  2. * 懒汉式单例模式
  3. */
  4. public class Singleton1 {
  5. //私有静态变量,用于存储单例实例
  6. private static Singleton1 instance;
  7. //构造私有方法,默认无参构造器失效,外部无法实例化
  8. private Singleton1(){
  9. }
  10. //获取单例实例的方法(新构造方法),相当于原来的new Singleton1()
  11. public static Singleton1 getInstance(){
  12. if (instance == null) {
  13. instance = new Singleton1();
  14. }
  15. return instance;
  16. }
  17. //其他普通方法
  18. public void doSomething(){
  19. System.out.println("懒汉式单例...普通方法...");
  20. }
  21. }

饿汉式单例模式,因为实例在类加载时就被创建,不存在多线程并发访问的问题,是线程安全的。实例在类加载时就创建,可能会占用一定的内存空间

  1. /**
  2. * 饿汉式单例模式
  3. * 饿汉式的特点是线程安全,因为实例在类加载时就被创建,不存在多线程并发访问的问题
  4. * 实例在类加载时就创建,可能会占用一定的内存空间
  5. */
  6. public class Singleton2 {
  7. //私有静态变量,类加载时创建对象
  8. private static Singleton2 instance = new Singleton2();
  9. //构造私有方法,默认无参构造器失效,外部无法实例化
  10. private Singleton2(){
  11. }
  12. //获取单例实例的方法(新构造方法)
  13. public static Singleton2 getInstance(){
  14. return instance;
  15. }
  16. //其他普通方法
  17. public void doSomething(){
  18. System.out.println("饿汉式单例...普通方法...");
  19. }
  20. }

懒汉式线程安全的单例模式,通过双重检查锁来保证线程安全性

  1. /**
  2. * 懒汉式单例模式(线程安全)
  3. */
  4. public class Singleton3 {
  5. //私有静态变量,用于存储单例实例
  6. private static Singleton3 instance;
  7. //构造私有方法,默认无参构造器失效,外部无法实例化
  8. private Singleton3(){
  9. }
  10. //获取单例实例的方法(新构造方法),相当于原来的new Singleton1()
  11. public static Singleton3 getInstance(){
  12. if (instance == null) {
  13. synchronized (Singleton3.class) {
  14. if (instance == null) {
  15. instance = new Singleton3();
  16. }
  17. }
  18. }
  19. return instance;
  20. }
  21. //其他普通方法
  22. public void doSomething(){
  23. System.out.println("懒汉式单例...普通(线程安全)方法...");
  24. }
  25. }

实例化调用

  1. public class Test {
  2. public static void main(String[] args) {
  3. //实例化Singleton1对象
  4. Singleton1 instance1 = Singleton1.getInstance();
  5. //实例化Singleton2对象
  6. Singleton2 instance2 = Singleton2.getInstance();
  7. //实例化Singleton3对象
  8. Singleton3 instance3 = Singleton3.getInstance();
  9. instance1.doSomething();
  10. instance2.doSomething();
  11. instance3.doSomething();
  12. }
  13. }

运行结果
在这里插入图片描述

优势

  1. 可以确保所有对象都访问唯一实例
  2. 类可以灵活更改实例化过程
  3. 减少内存开支和系统的性能开销

3. 原型模式

Java使用原型模式是为了通过复制现有对象来创建新的对象实例,以减少对象的创建开销和提高性能。它提供了一种便捷、高效、灵活的方式来创建对象的变体,并隐藏了对象创建细节,使代码更加简洁和易于维护。可以通过调用原型对象的clone()方法来创建新的对象,而无需显式地使用new关键字进行实例化。

使用示例代码

定义一个原型接口,并定义clone()的抽象方法

  1. /**
  2. * 定义一个原型接口
  3. */
  4. public interface Prototype {
  5. Prototype clone();
  6. }

原型类A实现原型接口,并重写clone()方法

  1. /**
  2. * 原型实现类
  3. */
  4. public class PrototypeA implements Prototype {
  5. private String name;
  6. private Integer age;
  7. public PrototypeA(String name, Integer age) {
  8. this.name = name;
  9. this.age = age;
  10. }
  11. public String getName() {
  12. return name;
  13. }
  14. public void setName(String name) {
  15. this.name = name;
  16. }
  17. public Integer getAge() {
  18. return age;
  19. }
  20. public void setAge(Integer age) {
  21. this.age = age;
  22. }
  23. @Override
  24. public String toString() {
  25. return "PrototypeA{" +
  26. "name='" + name + '\'' +
  27. ", age=" + age +
  28. '}';
  29. }
  30. @Override
  31. public Prototype clone() {
  32. return new PrototypeA(name, age);
  33. }
  34. }

原型模式可以方便地创建和复制对象,避免了重复的对象实例化过程,并且可以动态地添加或修改对象的属性

  1. public class Test {
  2. public static void main(String[] args) {
  3. PrototypeA prototype1 = new PrototypeA("张三", 18);
  4. System.out.println("原型对象:"+prototype1);
  5. //克隆原型对象
  6. PrototypeA prototype2 = (PrototypeA)prototype1.clone();
  7. System.out.println("克隆对象:"+prototype2);
  8. prototype2.setName("李四");
  9. System.out.println("修改后克隆对象:"+prototype2);
  10. }
  11. }

运行结果
在这里插入图片描述

优势

  1. 减少对象的创建开销
  2. 提高性能
  3. 对象创建的隐藏

4. 适配器模式

Java使用适配器模式可以将一个类的接口转换成客户端所期望的另一个接口,解决由于接口不兼容而导致不能正常工作的问题。提供了一种灵活、可扩展的方式来复用现有功能、解耦合并提高系统的兼容性,可以帮助我们在不改变已有代码结构的前提下,实现不同类之间的协同工作。

使用示例代码

定义一个目标接口,在接口中定义一个抽象方法request()

  1. /**
  2. * 目标接口
  3. */
  4. public interface Target {
  5. void request();
  6. }

定义一个需要适配的类Adaptee,定义一个测试的适配方法specificRequest()

  1. /**
  2. * 需要适配的类
  3. */
  4. public class Adaptee {
  5. public void specificRequest(){
  6. System.out.println("需要适配的方法!");
  7. }
  8. }

定义适配器类Adapter实现目标接口Target,并重写request()方法。在适配器类中定义需要适配类Adaptee的成员变量,并在构造方法中赋值

  1. /**
  2. * 适配器类
  3. */
  4. public class Adapter implements Target {
  5. private Adaptee adaptee;
  6. public Adapter(Adaptee adaptee) {
  7. this.adaptee = adaptee;
  8. }
  9. @Override
  10. public void request() {
  11. adaptee.specificRequest();
  12. }
  13. }

适配器调用目标接口的方法,实际上会调用需要适配类Adaptee的特定方法

  1. public class Test {
  2. public static void main(String[] args) {
  3. //创建需要适配的对象
  4. Adaptee adaptee = new Adaptee();
  5. //创建适配器类对象,传入需要适配的对象
  6. Target adapter = new Adapter(adaptee);
  7. //调用目标接口的方法,实际上会调用需要适配类中的特定方法
  8. adapter.request();
  9. }
  10. }

运行结果
在这里插入图片描述

优势

  1. 可以解决两个接口不兼容的问题
  2. 功能复用,让已经存在的类在新的环境中复用
  3. 提高系统的扩展性和灵活性

5. 观察者模式

Java使用观察者模式是为了实现对象之间的松耦合和事件驱动的通信机制,以便在一个对象的状态变化时通知其他相关对象。提供了一种灵活、可扩展和解耦合的方式来实现发布者和订阅者之间的通信,使得对象能够根据需要进行状态观察和响应,从而实现更灵活、可维护和可扩展的代码设计。

使用示例代码

定义一个观察者接口Observer,在接口中定义一个抽象方法update()

  1. /**
  2. * 定义一个观察者接口
  3. */
  4. public interface Observer {
  5. void update();
  6. }

定义一个主题接口,并定义注册、注销、通知变化

  1. /**
  2. * 定义一个主题接口
  3. */
  4. public interface Subject {
  5. //注册观察者
  6. void registerObserver(Observer observer);
  7. //注销观察者
  8. void unregisterObserver(Observer observer);
  9. //通知观察者(状态变化)
  10. void notifyObservers();
  11. }

主题具体实现类,重写三个抽象方法,并且在成员变量中引入Observer和state值

  1. /**
  2. * 主题具体实现类
  3. */
  4. public class SubjectA implements Subject {
  5. private List<Observer> observers = new ArrayList<>();
  6. private int state;
  7. public int getState() {
  8. return state;
  9. }
  10. public void setState(int state) {
  11. this.state = state;
  12. notifyObservers();
  13. }
  14. @Override
  15. public void registerObserver(Observer observer) {
  16. observers.add(observer);
  17. }
  18. @Override
  19. public void unregisterObserver(Observer observer) {
  20. observers.remove(observer);
  21. }
  22. @Override
  23. public void notifyObservers() {
  24. for (Observer observer : observers) {
  25. observer.update();
  26. }
  27. }
  28. }

观察者具体实现类,重写update()方法,并在构造方法中初始化主题类

  1. /**
  2. * 观察者接口具体实现类
  3. */
  4. public class ObserverA implements Observer {
  5. private SubjectA subject;
  6. public ObserverA(SubjectA subject) {
  7. this.subject = subject;
  8. this.subject.registerObserver(this);
  9. }
  10. public void cancelObserver() {
  11. this.subject.unregisterObserver(this);
  12. }
  13. @Override
  14. public void update() {
  15. int state = this.subject.getState();
  16. //执行相应操作
  17. System.out.println(this+" 此时状态为:"+state);
  18. }
  19. }

当主题的状态发生变化时,会通知所有的观察者进行相应的更新操作,通过观察者模式可以有效实现对象之间的解耦和消息传递

  1. public class Test {
  2. public static void main(String[] args) {
  3. SubjectA subject = new SubjectA();
  4. ObserverA observer1 = new ObserverA(subject);
  5. ObserverA observer2 = new ObserverA(subject);
  6. ObserverA observer3 = new ObserverA(subject);
  7. System.out.println("1号观察者:"+observer1);
  8. System.out.println("2号观察者:"+observer2);
  9. System.out.println("3号观察者:"+observer3);
  10. subject.setState(12);
  11. System.out.println("主题状态发生变化!!!");
  12. subject.setState(24);
  13. observer2.cancelObserver();
  14. System.out.println("2号观察者退出!!!");
  15. System.out.println("主题状态发生变化!!!");
  16. subject.setState(36);
  17. }
  18. }

运行结果
在这里插入图片描述

优势

  1. 提高代码的可维护性和可扩展性
  2. 提升扩展性和降低耦合度
  3. 事件驱动的机制能够及时地响应状态变化,实现对象之间的即时通信和协作

&nbap;

6. 策略模式

Java的策略模式可以在运行时根据不同的情况选择不同的算法或策略,以实现灵活、可扩展和可替换的行为。策略模式将算法封装在独立的策略类中,避免了大量的条件语句,提高了代码的可读性和可维护性。它能够灵活地应对需求的变化,并支持动态切换算法,从而使系统更加灵活、可扩展和易于修改。

使用示例代码

定义策略接口,在接口中定义一个抽象方法execute()

  1. /**
  2. * 抽象策略接口
  3. */
  4. public interface Strategy {
  5. void execute();
  6. }

具体策略类A,重写抽象方法

  1. /**
  2. * 具体策略类A
  3. */
  4. public class StrategyA implements Strategy{
  5. @Override
  6. public void execute() {
  7. System.out.println("执行策略A...");
  8. }
  9. }

具体策略类B,重写抽象方法

  1. /**
  2. * 具体策略类B
  3. */
  4. public class StrategyB implements Strategy{
  5. @Override
  6. public void execute() {
  7. System.out.println("执行策略B...");
  8. }
  9. }

上下文类

  1. /**
  2. * 上下文类
  3. */
  4. public class Context {
  5. private Strategy strategy;
  6. public Context(Strategy strategy) {
  7. this.strategy = strategy;
  8. }
  9. public void executeStrategy() {
  10. strategy.execute();
  11. }
  12. }

创建策略对象,创建上下文对象,并将具体策略对象传入,执行、切换不同的策略

  1. public class Test {
  2. public static void main(String[] args) {
  3. //创建策略对象
  4. Strategy strategyA = new StrategyA();
  5. Strategy strategyB = new StrategyB();
  6. //创建上下文对象,并将具体策略对象传入
  7. Context context = new Context(strategyA);
  8. System.out.println("初始化策略A!!!");
  9. //执行策略A
  10. context.executeStrategy();
  11. //切换策略为B
  12. context = new Context(strategyB);
  13. System.out.println("切换为策略B!!!");
  14. //执行策略B
  15. context.executeStrategy();
  16. }
  17. }

运行结果
在这里插入图片描述

优势

  1. 可以避免大量的条件语句,将各种不同的算法或行为封装在独立的策略类中
  2. 提升可扩展性与灵活性
  3. 算法或行为可以被独立地演化和替换
  4. 单一职责原则,代码结构更加清晰和可维护

7. 装饰者模式

Java使用装饰者模式可以在不修改原始对象的情况下,动态地扩展其功能或添加新的功能。具有很好的灵活性和可扩展性,可以有效避免类层次结构的膨胀和代码的修改,符合设计模式中的开放封闭原则和单一职责原则。

使用示例代码

定义组件接口,在接口中定义一个抽象方法operation()

  1. /**
  2. * 定义抽象组件接口
  3. */
  4. public interface Component {
  5. void operation();
  6. }

具体组件类,实现组件接口,并重写operation()方法

  1. /**
  2. * 具体组件类
  3. */
  4. public class MyComponent implements Component{
  5. @Override
  6. public void operation() {
  7. System.out.println("执行具体组件操作!");
  8. }
  9. }

抽象装饰者方法,实现组件接口

  1. /**
  2. * 抽象装饰者方法
  3. */
  4. public abstract class Decorator implements Component {
  5. private Component component;
  6. public Decorator(Component component) {
  7. this.component = component;
  8. }
  9. public void operation(){
  10. component.operation();
  11. }
  12. }

具体装饰者类A

  1. /**
  2. * 具体装饰者类A
  3. */
  4. public class MyDecoratorA extends Decorator {
  5. public MyDecoratorA(Component component) {
  6. super(component);
  7. }
  8. public void operation(){
  9. super.operation();
  10. addBehavior();
  11. }
  12. public void addBehavior(){
  13. System.out.println("装饰者A新增的行为...");
  14. }
  15. }

具体装饰者类B

  1. /**
  2. * 具体装饰者类B
  3. */
  4. public class MyDecoratorB extends Decorator {
  5. public MyDecoratorB(Component component) {
  6. super(component);
  7. }
  8. public void operation(){
  9. super.operation();
  10. addBehavior();
  11. }
  12. public void addBehavior(){
  13. System.out.println("装饰者B新增的行为...");
  14. }
  15. }

先创建装饰者A对象,参数传入组件对象。然后创建装饰者B对象,参数传入装饰者A对象,装饰者B执行的时候还会执行到执行者A的行为

  1. public class Test {
  2. public static void main(String[] args) {
  3. //创建具体组件对象
  4. Component component = new MyComponent();
  5. //创建装饰者A对象,参数传入组件对象
  6. Decorator decoratorA = new MyDecoratorA(component);
  7. //创建装饰者B对象,参数传入装饰者A对象
  8. Decorator decoratorB = new MyDecoratorB(decoratorA);
  9. //装饰者B调用装饰后的操作
  10. decoratorB.operation();
  11. }
  12. }

运行结果
在这里插入图片描述

优势

  1. 对象功能的灵活扩展,而无需修改原始对象的结构
  2. 单一职责原则,将功能划分为独立的装饰器,每个装饰器只关注特定的功能
  3. 系统更加容易维护和扩展

8. 模板方法模式

Java的模板方法模式会定义一个算法的骨架结构,并允许子类在不改变该算法结构的情况下,重新实现某些步骤以提供特定的行为。通过将可变和具体实现的步骤留给子类来实现,模板方法模式让子类可以根据自己的需要来重写父类中的某些步骤,减少重复的代码,并提高代码的可维护性和可读性。

使用示例代码

定义抽象基类,抽象类定义一个模板方法,用于执行各类方法

  1. /**
  2. * 定义抽象基类
  3. */
  4. public abstract class MyAbstract {
  5. public void templateMethod(){
  6. concreteMethod1();
  7. abstractMethod();
  8. concreteMethod2();
  9. hookMethod();
  10. }
  11. //具体方法1
  12. public void concreteMethod1() {
  13. System.out.println("执行具体方法1");
  14. }
  15. //具体方法2
  16. public void concreteMethod2() {
  17. System.out.println("执行具体方法2");
  18. }
  19. //抽象方法
  20. public abstract void abstractMethod();
  21. //钩子方法
  22. public void hookMethod() {
  23. System.out.println("我应该不会被执行到,因为在子类中重写了!");
  24. }
  25. }

定义具体子类,重写抽象方法和钩子方法

  1. /**
  2. * 具体子类
  3. */
  4. public class MyConcrete extends MyAbstract{
  5. @Override
  6. public void abstractMethod() {
  7. System.out.println("抽象方法执行具体方法的子类实现");
  8. }
  9. @Override
  10. public void hookMethod() {
  11. System.out.println("执行钩子方法");
  12. }
  13. }

MyAbstract根据实际需求实现了抽象方法,并可以选择性地重写钩子方法,最终会按照顺序调用具体方法、抽象方法和钩子方法

  1. public class Test {
  2. public static void main(String[] args) {
  3. MyAbstract myAbstract = new MyConcrete();
  4. myAbstract.templateMethod();
  5. }
  6. }

运行结果
在这里插入图片描述

优势

  1. 减少代码冗余,复用率高
  2. 提供一致的算法结构,保持算法的一致性和稳定性
  3. 可扩展性和灵活性强
  4. 减少重复的代码,并提高代码的可维护性和可读性

总结

本文重点介绍了八种设计模式的原理及使用方法,当然实际业务开发中不止这8种模式,大家可以按照自己业务架构的实际需求运用合适的设计模式。设计模式的必要性在于它们能够提供可复用、可维护、可扩展和高质量的代码解决方案。设计模式指导开发人员采用良好的设计原则和实践,使代码更具可读性和可维护性。通过合理地应用设计模式,可以降低软件开发风险,提高开发效率,创建高质量的软件系统。

发表评论

表情:
评论列表 (有 0 条评论,151人围观)

还没有评论,来说两句吧...

相关阅读

    相关 文章了解Java网络原理

    独立模式:计算机之间相互独立;随着时代的发展,越来越需要计算机之间互相通信,共享软件和数据,即以多个计算机协同⼯作来完成业务,就有了⽹络互连。⽹络互连:将多台计算机连接在...

    相关 文章了解Netty

    Netty 传统的IO模型的web容器,比如老版本的Tomcat,为了增加系统的吞吐量,需要不断增加系统核心线程数量,或者通过水平扩展服务器数量,来增加系统处理请求的能力