Java 设计模式

本文深入探讨了设计模式的概念,强调其提高代码可复用性、可维护性的重要性。文章介绍了23种设计模式,分为创建型、结构型和行为型三大类,并详细讲解了简单工厂模式、工厂方法模式、抽象工厂模式、单例模式、代理模式和装饰器模式等。此外,还阐述了设计模式的原则,如依赖倒置原则和开闭原则,并提供了实例演示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是设计模式?

 设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。设计模式一共有23种,这 23 种设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性,以及类的关联关系和组合关系的充分理解

23种设计模式 

分为三大类

创建型模式:工厂方法模式(Factory Method)抽象工厂模式(Abstract Factory)单例模式(Singleton)建造者模式(Builder)原型模式(Prototype)

在创建对象的同时隐藏创建逻辑,不使⽤ new 直接实例化对象,程序在判断需要创建哪些对象时更灵活

结构型模式:适配器模式(Adapter)装饰器模式(Decorator)代理模式(Proxy)外观模式(Facade)桥接模式(Bridge)组合模式(Composite)享元模式(Flyweight)

通过类和接⼝间的继承和引⽤实现创建复杂结构的对象

行为型模式:策略模式(Strategy)模板方法模式(Template Method)观察者模式(Observer)迭代器模式(Iterator)责任链模式(Chain of Responsibility)命令模式(Command)备忘录模式(Memento)状态模式(State)访问者模式(Visitor)中介者模式(Mediator)解释器模式(Interpreter)

 通过类之间不同通信方式实现不同行为

设计模式的原则

设计模式的原则有依赖导致原则、单一职责原则、接口隔离原则、开闭原则、迪米特法原则、里氏替换原则

简单工厂模式

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

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

Calendar 抽象类的⽇历对象。方法,调⽤方法根据不同的地区参数创建不同的Spring 中的 BeanFactory 使⽤简单工厂模式,根据传⼊⼀个唯⼀的标识来获得 Bean 对象

实例演示

/**
 * 拿铁、美式咖啡、卡布奇诺等均为咖啡家族的一种产品
 * 咖啡则作为一种抽象概念
 * @author 
 */
 
public abstract class Coffee {
 
    /**
     * 获取coffee名称
     * @return
     */
 
    public abstract String getName();
 
}
 
 
/**
 * 美式咖啡
 * @author
 *
 */
 
public class Americano extends Coffee {
 
 
    @Override
 
    public String getName() {
 
        return "美式咖啡";
    }
 
}
 
 
/**
 * 卡布奇诺
 * @author
 */
 
public class Cappuccino extends Coffee {
 
 
    @Override
 
    public String getName() {
 
        return "卡布奇诺";
 
    }
 
}
 
 
/**
 * 拿铁
 * @author
 */
 
public class Latte extends Coffee {
 
    @Override
 
    public String getName() {
 
        return "拿铁";
    }
 
}

工厂方法模式

工厂方法模式指定义⼀个创建对象的接⼝,让接⼝的实现类决定创建哪种对象,让类的实例化推迟到⼦类中进行。

客户端只需关⼼对应工厂而无需关⼼创建细节,主要解决了产品扩展的问题,在简单⼯⼚模式中如果产品种类变多,工厂的职责会越来越多,不便于维护。

Collection 接⼝这个抽象工厂中定义了⼀个抽象的 工厂方法,返回⼀个 Iterator 类的抽象产品。该⽅法通过 ArrayList 、HashMap 等具体工厂实现,返回 Itr、KeyIterator 等具体产品

抽象工厂模式

抽象工厂模式指提供⼀个创建⼀系列相关或相互依赖对象的接⼝,⽆需指定它们的具体类

客户端不依赖于产品类实例如何被创建和实现的细节,主要⽤于系统的产品有多于⼀个的产品族,⽽系统只消费其中某⼀个产品族产品的情况

抽象工厂模式的缺点是不方便扩展产品族,并且增加了系统的 抽象性和理解难度

实例演示

public class AbstractFactoryTest {
 
   public static void main(String[] args) {
 
       // 抽象工厂
 
       String result = (new CoffeeFactory()).createProduct("Latte");
 
       System.out.println(result); // output:拿铁
 
   }
 
}
 
// 抽象工厂
 
abstract class AbstractFactory{
 
   public abstract String createProduct(String product);
 
}
 
// 啤酒工厂
 
class BeerFactory extends AbstractFactory{
 
   @Override
 
   public String createProduct(String product) {
 
       String result = null;
 
       switch (product) {
 
           case "Hans":
 
               result = "汉斯";
 
               break;
 
           case "Yanjing":
 
               result = "燕京";
 
               break;
 
           default:
 
               result = "其他啤酒";
 
               break;
 
       }
 
       return result;
 
   }
 
}
 
/\* \* 咖啡工厂 \*/
 
class CoffeeFactory extends AbstractFactory{
 
   @Override
 
   public String createProduct(String product) {
 
       String result = null;
 
       switch (product) {
 
           case "Mocca":
 
               result = "摩卡";
 
               break;
 
           case "Latte":
 
               result = "拿铁";
 
               break;
 
           default:
 
               result = "其他咖啡";
 
               break;
 
       }
 
       return result;
 
   }
 
}

单例模式

单例模式属于创建型模式,⼀个单例类在任何情况下都只存在⼀个实例,构造方法必须是私有的、由自己创建⼀个静态变量存储实例,对外提供⼀个静态公有方法获取实例

优点是内存中只有⼀个实例,减少了开销,尤其是频繁创建和销毁实例的情况下并且可以避免对资源的多重用。缺点是没有抽象层,难以扩展,与单⼀职责原则冲突

Spring 的 ApplicationContext 创建的 Bean 实例都是单例对象,还有 ServletContext、数据库连接池等也都是单例模式

实例演示

1 class Person{
 
 2 private:
 
 3     Person(){};
 
 4     Person(const Person&){};
 
 5
 
 6     static Person *Singleton; //静态变量,类内声明,类外初始化, 初始化时不用static,需指定作用域
 
 7
 
 8 public:
 
 9     static Person *getInstance(){
 
10         return Singleton;
 
11     }
 
12 };
 
13
 
14 Person *Person::Singleton = new Person;
 
15
 
16 void test01(){
 
17
 
18     Person::getInstance();//外部通过接口访问单例
 
19     Person *p = Person::getInstance();
 
20     Person *p1 = Person::getInstance();
 
21
 
22     //--------------
 
23     //不能直接定义或new实例,只能通过调用接口
 
24     //Person p2;
 
25     //Person *p1 = new(*p); //这里调用拷贝构造函数
 
26     //--------------
 
27
 
28     if (p == p1){
 
29         cout << "两个单例的地址相同,为同一个单例" << endl;
 
30     }
 
31
 
32 }

单例模式的实现有哪些

饿汉式:在类加载时就初始化创建单例对象,线程安全,但不管是否使⽤都创建对象可能会浪费内存

懒汉式:在外部调⽤时才会加载,线程不安全,可以加锁保证线程安全但效率低

双重检查锁:使⽤ volatile 以及多重检查来减⼩锁范围,提升效率

静态内部类:同时解决饿汉式的内存浪费问题和懒汉式的线程安全问题

枚举:不仅能避免线程安全问题,还能防⽌反序列化重新创建新的对象,绝对防⽌多次实例化,也能防⽌反射破解单例的问题

代理模式

代理模式属于结构型模式,为其他对象提供⼀种代理以控制对这个对象的访问。优点是可以增强⽬标对 象的功能,降低代码耦合度,扩展性好。缺点是在客户端和⽬标对象之间增加代理对象会导致请求处理速度变慢,增加系统复杂度

为其他对象提供一种代理以控制对这个对象的访问。在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层

实例演示

package com.Model.Proxy;
 
 
 
//买车行为
 
public interface BuyCar {
 
    public void buycar();
 
}
 
 
package com.Model.Proxy;
 
 
public class People implements BuyCar {
 
    private int cash;
 
    private String vip;
 
    private String username;
 
    @Override
 
    public void buycar() {
 
        System.out.println(username + " is vip so he/she can buy any car...");
 
    }
 
 
    public int getCash() {
 
        return cash;
 
    }
 
 
 
    public void setCash(int cash) {
 
        this.cash = cash;
 
    }
 
 
    public String getVip() {
 
        return vip;
 
    }
 
 
    public void setVip(String vip) {
 
        this.vip = vip;
 
    }
 
 
    public String getUsername() {
 
        return username;
 
    }
 
 
    public void setUsername(String username) {
 
        this.username = username;
 
    }
 
 
}
 
 
// 代理类 去检测买车行为是否符合规则
 
class ProxyBuyCar implements BuyCar {
 
    private People People;
 
    public People getPeople() {
 
        return People;
 
    }
 
    public void setPeople(People people) {
 
        People = people;
 
    }
 
 
    @Override
 
    public void buycar() {
 
        if (People.getVip().equals("vip")) {
 
            People.buycar();
 
        } else if (People.getCash() >= 50000) {
 
            System.out.println(People.getUsername() + "buy a new car trade over...");
 
        } else {
 
            System.out.println(People.getUsername() + "people can't buy a car ");
 
        }
 
    }
}

Spring 利⽤动态代理实现 AOP,如果 Bean 实现了接⼝就使⽤ JDK 代理,否则使⽤ CGLib 代理。

静态代理:代理对象持有被代理对象的引⽤,调⽤代理对象⽅法时也会调⽤被代理对象的⽅法,但是会 在被代理对象⽅法的前后增加其他逻辑。需要⼿动完成,在程序运⾏前就已经存在代理类的字节码⽂件,代理类和被代理类的关系在运⾏前就已经确定了。 缺点是⼀个代理类只能为⼀个⽬标服务,如果要服务多种类型会增加⼯作量

动态代理:动态代理在程序运⾏时通过反射创建具体的代理类,代理类和被代理类的关系在运⾏前是不确定的。动态代理的适⽤性更强,主要分为 JDK 动态代理和 CGLib 动态代理

JDK 动态代理:通过类的⽅法获取⼀个动态代理对象,需要传⼊三个参数,被代理对象的类加载器、被代理对象实现的接⼝,以及⼀个器来指明具体的逻辑,相⽐静态代理的优势是接⼝中声明的所有⽅法都被转移到调⽤处理的⽅法集中处理

CGLib 动态代理:JDK 动态代理要求实现被代理对象的接⼝,⽽ CGLib 要求继承被代理对象,如果⼀个类是 final 类则不能使⽤ CGLib 代理。两种代理都在运⾏期⽣成字节码,JDK 动态代理直接写字节码,⽽ CGLib 动态代理使⽤ ASM 框架写字节码,ASM 的⽬的是⽣成、转换和分析以字节数组表示的已编译 Java 类。 JDK 动态代理调⽤代理⽅法通过反射机制实现,⽽ GCLib 动态代理通过 FastClass 机制直接调⽤⽅法,它为代理类和被代理类各⽣成⼀个类,该类为代理类和被代理类的⽅法分配⼀个 int 参数,调⽤⽅法时可以直接定位,因此调⽤效率更⾼

装饰器模式

装饰器模式属于结构型模式,在不改变原有对象的基础上将功能附加到对象,相⽐继承可以更加灵活地 扩展原有对象的功能

装饰器模式适合的场景:在不想增加很多⼦类的前提下扩展⼀个类的功能

java.io 包中InputStream 字节输⼊流通过装饰器 BufferedInputStream 增强为缓冲字节输⼊流

适配器模式

适配器模式属于结构型模式,它作为两个不兼容接⼝之间的桥梁,结合了两个独⽴接⼝的功能,将⼀个 类的接⼝转换成另外⼀个接⼝使得原本由于接⼝不兼容⽽不能⼀起⼯作的类可以⼀起⼯作

缺点是过多使⽤适配器会让系统⾮常混乱,不易整体把握

java.io 包中,InputStream 字节输⼊流通过适配器 InputStreamReader 转换为 Reader 字符输⼊流

Spring MVC 中的 HandlerAdapter,由于 handler 有很多种形式,包括 Controller、HttpRequestHandler、Servlet 等,但调用方式⼜是确定的,因此需要适配器来进⾏处理,根据适配规则调⽤ handle 方法

Arrays.asList ⽅法,将数组转换为对应的集合(注意不能使⽤修改集合的⽅法,因为返回的 ArrayList是 Arrays 的⼀个内部类)

实例演示

/\* \* 传统的充电线 MicroUSB \*/
 
interface MicroUSB {
 
    void charger();
 
}
 
/\* \* TypeC 充电口 \*/
 
interface ITypeC {
 
    void charger();
 
}
 
class TypeC implements ITypeC {
 
    @Override
 
    public void charger() {
 
        System.out.println("TypeC 充电");
 
    }
 
}
 
/\* \* 适配器 \*/
 
class AdapterMicroUSB implements MicroUSB {
 
    private TypeC typeC;
 
    public AdapterMicroUSB(TypeC typeC) {
 
        this.typeC = typeC;
 
    }
 
    @Override
 
    public void charger() {
 
        typeC.charger();
 
    }
 
}
 
/\* \* 测试调用 \*/
 
public class AdapterTest {
 
    public static void main(String[] args) {
 
        TypeC typeC = new TypeC();
 
        MicroUSB microUSB = new AdapterMicroUSB(typeC);
 
        microUSB.charger();
 
    }
 
}

适配器模式和和装饰器模式以及代理模式的区别

  • 适配器模式没有层级关系,适配器和被适配者没有必然连续,满⾜ has-a 的关系,解决不兼容的问题, 是⼀种后置考虑

  • 装饰器模式具有层级关系,装饰器与被装饰者实现同⼀个接⼝,满⾜ is-a 的关系,注重覆盖和扩展,是⼀种前置考虑

  • 适配器模式主要改变所考虑对象的接⼝,⽽代理模式不能改变所代理类的接⼝

策略模式

策略模式属行为模型模式,定义了⼀系列算法并封装起来,之间可以互相替换。策略模式主要解决在有多种算法相似的情况下,使⽤ if/else 所带来的难以维护

优点是算法可以⾃由切换,可以避免使用多重条件判断并且扩展性良好,缺点是策略类会增多并且所有 策略类都需要对外暴露

在集合框架中,经常需要通过构造方法传⼊⼀个比较器 Comparator 进行比较排序

Comparator 就是⼀个抽象策略,⼀个类通过实现该接⼝并重写 compare 方法成为具体策略类

创建线程池时,需要传⼊拒绝策略,当创建新线程使当前运⾏的线程数超过 maximumPoolSize 时会使⽤相应的拒绝策略处理

实例演示

 
/\* \* 声明旅行 \*/
 
interface ITrip {
 
    void going();
 
}
 
class Bike implements ITrip {
 
    @Override
 
    public void going() {
 
        System.out.println("骑自行车");
 
    }
 
}
 
class Drive implements ITrip {
 
    @Override
 
    public void going() {
 
        System.out.println("开车");
 
    }
 
}
 
/\* \* 定义出行类 \*/
 
class Trip {
 
    private ITrip trip;
 
 
 
    public Trip(ITrip trip) {
 
        this.trip = trip;
 
    }
 
 
 
    public void doTrip() {
 
        this.trip.going();
 
    }
 
}
 
/\* \* 执行方法 \*/
 
public class StrategyTest {
 
    public static void main(String[] args) {
 
        Trip trip = new Trip(new Bike());
 
        trip.doTrip();
 
    }
 
}

模板模式

模板模式属于⾏为型模式,使⼦类可以在不改变算法结构的情况下重新定义算法的某些步骤,适⽤于抽 取⼦类重复代码到公共⽗类

优点是可以封装固定不变的部分,扩展可变的部分。缺点是每⼀个不同实现都需要⼀个⼦类维护,会增加类的数量

为防⽌恶意操作,⼀般模板⽅法都以 final 修饰

HttpServlet 定义了⼀套处理 HTTP 请求的模板,service ⽅法为模板⽅法,定义了处理HTTP请求的基本流程,doXXX 等⽅法为基本⽅法,根据请求⽅法的类型做相应的处理,⼦类可重写这些⽅法

观察者模式

观察者模式属于⾏为型模式,也叫发布订阅模式,定义对象间的⼀种⼀对多的依赖关系,当⼀个对象的 状态发⽣改变时,所有依赖于它的对象都得到通知并被⾃动更新。主要解决⼀个对象状态改变给其他对象通知的问题,缺点是如果被观察者对象有很多的直接和间接观察者的话通知很耗时, 如果存在循环依赖的话可能导致系统崩溃,另外观察者⽆法知道⽬标对象具体是怎么发⽣变化的

ServletContextListener 能够监听 ServletContext 对象的⽣命周期,实际上就是监听 Web 应⽤。当Servlet 容器启动 Web 应⽤时调⽤contextInitialized方法,终⽌时调⽤ contextDestroyed方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值