23种设计模式详解(上)+https://developer.aliyun.com/article/1625049
例子
package com.linghu.demo05; /** * @author linghu * @date 2024/2/4 11:12 */ public class Client { public static void main(String[] args) { //创建经纪人类 Agent agent = new Agent(); //创建明星类 Star star = new Star("李小龙"); agent.setStar(star); //创建粉丝类 Fans fans = new Fans("令狐"); agent.setFans(fans); //创建公司对象 Company company = new Company("好莱坞"); agent.setCompany(company); //开会和洽谈业务~ agent.meeting(); agent.business(); } }
合成复用原则
基本介绍
合成复用原则是指:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
例子
创建者模式
基本介绍
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。
创建型模式分为:
- 单例模式
- 工厂方法模式
- 抽象工程模式
- 原型模式
- 建造者模式
单例设计模式
饿汉式-方法1:静态变量
package com.linghu.demo06.pattern; /** * 饿汉式 * 静态变量-创建类对象 * @author linghu * @date 2024/2/4 14:41 */ public class Singleton { //1、私有化构造器,防止外部创建该类对象,只能让外部通过get方法访问本类 private Singleton(){ } //2、在成员位置创建该类对象 private static Singleton instance=new Singleton(); //3、对外提供静态方法获取该类对象 public static Singleton getInstance(){ return instance; } }
饿汉式-方法2:静态代码块
这个方法跟上面的方法其实差不多。
package com.linghu.demo06.pattern; /** * 饿汉式 * 静态变量-创建类对象 * @author linghu * @date 2024/2/4 14:41 */ public class Singleton { //1、私有化构造器,防止外部创建该类对象,只能让外部通过get方法访问本类 private Singleton(){ } //2、在成员位置创建该类对象 private static Singleton instance;//null static { instance=new Singleton(); } //3、对外提供静态方法获取该类对象 public static Singleton getInstance(){ return instance; } }
总结
以上 Singleton类保证了只能对外创建一个对象。
懒汉式-方式1(线程不安全)
这个方式就是用饿汉式的第一种方式进行调整就可以了:
public class Singleton { //1、私有化构造器,防止外部创建该类对象,只能让外部通过get方法访问本类 private Singleton(){ } //2、在成员位置创建该类对象 private static Singleton instance; //3、对外提供静态方法获取该类对象 public static Singleton getInstance(){ //instance=new Singleton();//在这里做了调整,使用该对象的时候再调用,就不会浪费了 if (instance==null){ instance=new Singleton(); } return instance; } }
如上的代码中,我们加入了一个if
判断,这里的操作叫做 懒加载!
在开发中,如果某个实例的创建需要消耗很多系统资源,那么我们通常会使用惰性加载机制,也就是说只有当使用到这个实例的时候才会创建这个实例,这个好处在单例模式中得到了广泛应用。这个机制在单线程环境下的实现非常简单,然而在多线程环境下却存在隐患。
懒汉式-方式2(线程安全)
在上面懒加载的情况下加入一个 synchronized
同步锁就可以了:
package com.linghu.demo06.pattern01; /** * @author linghu * @date 2024/2/4 17:01 */ public class Singleton { //1、构造器私有化 private Singleton(){ } //2、创建类对象 private static Singleton instance; //3、对外提供静态方法获取该对象 public static synchronized Singleton getInstance(){ if (instance==null){//当线程1进入以后,先判断,线程2在方法外面等待 //等待到线程1对象创建或者返回完毕释放锁以后,线程2才开始进入 instance=new Singleton(); } return instance; } }
**该方式也实现了懒加载效果,同时又解决了线程安全问题。但是在getInstance()方法上添加了synchronized关键字,导致该方法的执行效果特别低。 **
懒汉式-方式3(双重检查锁)
**懒汉模式中加锁的问题,对于 getInstance() 方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的实现模式:双重检查锁模式 **
package com.linghu.demo07; /** * 双重检查方式 * @author linghu * @date 2024/2/5 10:33 */ public class Singleton { //1、私有构造器 private Singleton(){} private static Singleton instance; //对外提供静态方法获取该对象 public static Singleton getInstance(){ //第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例 if (instance==null){ synchronized (Singleton.class){ //第二次判断,抢到锁以后是否为null if (instance==null){ instance=new Singleton(); } } } return instance; } }
双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。要解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile 关键字, volatile 关键字可以保证可见性和有序性。
懒汉式-方式4(静态内部类)
**静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由于被static 修饰,保证只被实例化一次,并且严格保证实例化顺序。 **
package com.linghu.demo08; /** * @author linghu * @date 2024/2/5 11:21 */ public class Singleton { //1、私有化构造器 private Singleton(){} //2、静态内部类提供实例 private static class SingletonHolder{//这个类只能内部用,外界访问不了 //调用getInstance方法的时候才能初始化INSTANCE,而且只会被调用一次 private static final Singleton INSTANCE=new Singleton(); } //3、对外提供静态方法获取该对象 public static Singleton getInstance(){ return SingletonHolder.INSTANCE; } }
第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder并初始化INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。
枚举方式-饿汉式(不考虑内存空间)
**枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。 **
package com.linghu.demo09; /** * 枚举方式 * @author linghu * @date 2024/2/5 11:34 */ public enum Singleton { INSTANCE; }
JDK源码解析-RunTime类的单例设计模式
在jdk源码中,RunTime类的设计其实就用到了单例设计模式中的恶汉式来实现的。
如上可以看出代码中提供了私有化构造器、静态访问对象的方法、静态变量对象:
public class Runtime { private static Runtime currentRuntime = new Runtime(); /** * Returns the runtime object associated with the current Java application. * Most of the methods of class <code>Runtime</code> are instance * methods and must be invoked with respect to the current runtime object. * * @return the <code>Runtime</code> object associated with the current * Java application. */ public static Runtime getRuntime() { return currentRuntime; } /** Don't let anyone else instantiate this class */ private Runtime() {}
工厂模式
在Java中万物皆对象,创建对象通过都是new出来的,但这么干会出现一些问题:比如说,当我们需要更换对象的时候,所有new对象的地方我们都需要修改一遍,这就违背了软件设计的 开闭原则!
引入工厂模式的目的是为了:解耦!
也就是说,我们可以利用工厂来生产对象,我们开发者只需要和工厂打交道即可,彻底和对象解耦!
三种工厂:
- 简单工厂模式(不属于GOF的23种经典设计模式)
- 工厂方法模式
- 抽象工厂模式
引例
package com.linghu.demo10; /** * @author linghu * @date 2024/2/5 16:20 */ public abstract class Coffee { //获取咖啡的名字,定义成抽象方法 public abstract String getName(); public void addMilk(){ System.out.println("加奶~"); } public void addSugar(){ System.out.println("加糖~"); } }
package com.linghu.demo10; /** * @author linghu * @date 2024/2/5 16:31 */ public class CoffeeStore { //根据不同的咖啡类型生成并返回不同的咖啡对象 public Coffee orderCoffee(String type){ Coffee coffee=null; if ("america".equals(type)){ coffee=new AmericaCoffee(); }else if ("latte".equals(type)){ coffee=new LatteCoffee(); } //添加配料 coffee.addMilk(); coffee.addSugar(); return coffee; } }
package com.linghu.demo10; /** * 美式咖啡 * @author linghu * @date 2024/2/5 16:24 */ public class AmericaCoffee extends Coffee{ @Override public String getName() { return "美式咖啡"; } }
package com.linghu.demo10; /** * @author linghu * @date 2024/2/5 16:29 */ public class LatteCoffee extends Coffee{ @Override public String getName() { return "拿铁咖啡"; } }
package com.linghu.demo10; /** * @author linghu * @date 2024/2/5 16:38 */ public class Client { public static void main(String[] args) { //1、创建咖啡店类 CoffeeStore store=new CoffeeStore(); //2、点咖啡 Coffee orderCoffee = store.orderCoffee("america"); System.out.println(orderCoffee.getName()); } }
简单工厂模式
简单工厂模式不是一种模式,而是一种编码习惯。
结构
简单工厂模式包含结构如下:
- 抽象产品:定义了产品的规范,描述了产品的主要特征和功能。
- 具体产品:实现或者继承抽象产品的子类。
- 具体工厂:提供创建产品的方法,调用者通过该方法获取产品。
实现
对上述咖啡案例的改进就在于: 要将咖啡店和咖啡产品进行解耦工作,引入工厂模式。
工厂类代码如下:
package com.linghu.demo10; /** * 这个工厂就负责制造咖啡,里面没有咖啡产品,你要替换咖啡 * 产品,不需要动这里的代码,这就实现了咖啡店和咖啡产品的解耦! * 咖啡店只需要调用这个工厂就可以生产咖啡了,不需要关注咖啡产品了! * @author linghu * @date 2024/2/5 17:11 */ public class SimpleCoffeeFactory { public Coffee createCoffee(String type){ Coffee coffee=null; if ("americano".equals(type)){ coffee=new AmericaCoffee(); }else if ("latte".equals(type)){ coffee=new LatteCoffee(); } return coffee; } }
法,调用者通过该方法获取产品。
实现
对上述咖啡案例的改进就在于: 要将咖啡店和咖啡产品进行解耦工作,引入工厂模式。
[外链图片转存中…(img-EbAXEhcI-1711960898263)]
工厂类代码如下:
package com.linghu.demo10; /** * 这个工厂就负责制造咖啡,里面没有咖啡产品,你要替换咖啡 * 产品,不需要动这里的代码,这就实现了咖啡店和咖啡产品的解耦! * 咖啡店只需要调用这个工厂就可以生产咖啡了,不需要关注咖啡产品了! * @author linghu * @date 2024/2/5 17:11 */ public class SimpleCoffeeFactory { public Coffee createCoffee(String type){ Coffee coffee=null; if ("americano".equals(type)){ coffee=new AmericaCoffee(); }else if ("latte".equals(type)){ coffee=new LatteCoffee(); } return coffee; } }
**后期如果再加新品种的咖啡,我们势必要需求修改SimpleCoffeeFactory的代码,违反了开闭原则。工厂类的客户端可能有很多,比如创建美团外卖等,这样只需要修改工厂类的代码,省去其他的修改操作。 **