学习笔记-Java设计模式和设计原则到实战(含jdk源代码)

下面的例子都是从JDK 21的标准库源代码里面截取的,源代码在${JAVA_HOME}/lib/src.zip,解压就ok了

单例模式

分为饿汉式、懒汉式。确保一个类只能有一个实例,无需频繁创建和销毁对象,节约了系统资源,提高系统的性能

Runtime类就是典型的单例模式,因为获取其实例是Runtime.getRuntime()而不是new Runtime()。new出来的是不同的对象实例,而getRuntime()返回的总是同一个对象引用。

饿汉式

我觉得sun.misc.Unsafe类就比较典型,每次获取实例都要使用Unsafe#getUnsafe方法

public final class Unsafe {
	private Unsafe() {} // 私有化构造器

	private static final Unsafe UNSAFE = new Unsafe(); // 必须得是静态的!!

	public static Unsafe getUnsafe() {
		return UNSAFE;
	}
}

懒汉式

就是懒加载,获取时才创建实例

这里有个synchronized防止多线程并发获取是多new
你可能疑惑为什么要检查两次null。因为为了防止多线程并发时,第一次检查完后怕有别的线程已经创建了,所以再检查一次

public class System {
	private static volatile Console cons;
	
	public Console console() {
		if (cons == null) {
			synchronized (System.class) {
				if (cons == null) {
					cons = SharedSecrets.getJavaIOAccess().console();
				}
			}
		}
		return cons;
	}
}

工厂模式

工厂差不多就是传入一个对象,然后工厂内部一顿set猛如虎后输出

private static class DefaultThreadFactory implements ThreadFactory {
	private static final AtomicInteger poolNumber = new AtomicInteger(1);
	private final ThreadGroup group;
	private final AtomicInteger threadNumber = new AtomicInteger(1);
	private final String namePrefix;

	DefaultThreadFactory() {
		@SuppressWarnings("removal")
		SecurityManager s = System.getSecurityManager();
		group = (s != null) ? s.getThreadGroup() :
		                      Thread.currentThread().getThreadGroup();
		namePrefix = "pool-" +
		 			 poolNumber.getAndIncrement() +
		 			 "-thread-";
	}

	public Thread newThread(Runnable r) {
		Thread t = new Thread(group, r,
		 					  namePrefix + threadNumber.getAndIncrement()
		 					  0);
		if (t.isDaemon())
			t.setDaemon(false);
		if (t.getPriority() != Thread.NORM_PRIORITY)
			t.setPriority(Thread.NORM_PRIORITY);
		return t;
	}
}

@Test
void main() {
	var f = new DefaultThreadFactory();
	f.newThread(() -> System.out.println("Hello, World!")).start();
}

工厂方法模式

让我们来回顾一段代码

import java.util.ExecutorService;
import java.util.Executors;

// JDK 21预览匿名类
void main() {
	ExecutorService pool = Executors.newFixedThreadExecutor(1);
	Future<String> future = pool.submit(() -> "Hello, World!");
	// ...
}

Executors的各个静态方法就是ExecutorService的工厂方法

适配器模式

下面那个例子非常典型,来自于java.io.FileFile其实调用的是接口FileSystem

public class File {
	private static final FileSystem FS = DefaultFileSystem.getFileSystem();
}

接下来DefaultFileSystem#getFileSystem就有意思了,因为不同的平台代码是不同的

final class DefaultFileSystem {
	private DefaultFileSystem() {}

	public static FileSystem getFileSystem() {
		// MacOS, Linux, Unix
		return new java.io.UnixLikeFileSystem();
		// Windows NT Kernal
		return new java.io.Win32NTFileSystem()
	}
}

策略模式

有些方法体现了策略模式,譬如:
List.sort(Comparator)
我们可以自定义比较器,生序排序:
l.sort((o1, o2) -> o1.compareTo(o2))
倒序排序:
l.sort((o1, o2) -> o2.compareTo(o1))
你应该可以传一个null,将会使用默认的比较器

外观模式

主要跟封装有关。封装到用户无需关注底层实现。譬如java.lang.Math类,其封装的其实是java.lang.StrictMath,然后最终实现是java.lang.FdLibm。所以MathFdLibm的外观

设计原则

单一职责原则(Single Responsibility Principle, SRP):一个类只应该有一个引起变化的原因。换句话说,一个类应该只有一个职责,只有一个改变它的原因。这样可以降低类的复杂度,提高可维护性。

最典型的例子就是java.util.ArrayList#sort

主要代码周围是并发修改检查,不允许超过一个线程修改,否则panic

    @Override
    @SuppressWarnings("unchecked")
    public void sort(Comparator<? super E> c) {
        final int expectedModCount = modCount; 
		Arrays.sort((E[]) elementData, 0, size, c); // <=== 主要代码
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        modCount++;
    }

elementData是ArrayList的一个成员变量,储存数据的数组,签名为:

transient Object[] elementData;

调用的其实是java.util.Arrays#sort,这是一个数组工具类,源代码

public final class Arrays {
    public static void sort(int[] a) {
        DualPivotQuicksort.sort(a, 0, 0, a.length);
    }
}

java.util.DualPivotQuicksort类集成了各种排序算法,包括插入排序和计数排序,最主要还是双端快排,这是快速排序的加强版,源代码就不放了,就是你平常写的那些排序,但不过写的比你的好一点,没有更多封装了。感兴趣的可以看看源代码实现。

java.util.ArrayList
java.util.Arrays负责集成各种数组工具方法,排序算法体格太大了应该分开
java.util.DualPivotQuicksort负责排序算法的实现

实战

下面的例子涉及到 SSM 框架中的 MyBatis。初学者,基础不好的,或者大一生请绕行,除非你特别感兴趣,否则可能消磨你的学习兴趣。

假设我们要用 Apache MyBatis 框架写一个数据库持久业务,我们可以把业务代码和初始化 SqlSessionFactory的代码分开写,独立写成一个工厂类MyBatisFactories#getSqlSessionFactory如下:

package demo.mybatis.util;

import org.apache.ibatis.io.SqlSession;
import org.apache.ibatis.io.SqlSessionFactory;
import org.apache.ibatis.io.SqlSessionFactoryBuilder;
import org.apache.ibatis.io.Resources;

public final class MyBatisFactories {
	private MyBatisFactories() { throw new InternalError(); }
	
	public static SqlSessionFactory getSqlSessionFactory() {
		return sessionFactory();
	}
	
	public static SqlSession openSession() {
		return getSqlSessionFactory().openSession();
	}
	
	private static final SqlSessionFactory sessionFactory;
	static {
		try {
			final String cfg = MyBatis.getProperty("configFilename");
			sessionFactory = new SqlSessionFactoryBuilder()
				.build(Resources.getResourceAsStream(cfg));
		} catch (Exception e) {
			throw new ExceptionInInitializerError(e);
		}
	}
}

初始化SqlSessionFactory一定要在 static代码块里保证其只加载一次。多次加载会浪费I/O资源。我相信你的教授曾经跟你讲过,如果没有,那就记住

package demo.mybatis.util;

import java.util.Properties;

public class MyBatis {
	public static String getProperty(String name) {
		return MyBatisProperties.props.get(name);
	}

	static class MyBatisProperties {
		static final Properties props = new Properties();
		static {
			props.load(new FileInputStream("mybatis-cfg.properties"));
		}
	}
}

最终DAO业务代码变得这么简洁:

import demo.mybatis.util.MyBatisFactories;
import demo.mybatis.mapper.UserMapper;

@Test
void main() {
	var session = MyBatisFactories.openSession();
	try {
		UserMapper mapper = session.getMapper(UserMapper.class);
		mapper.insertUser(new User("root", "toor"));
	} catch (Exception e) {
		session.rollback();
		throw e;
	} finally {
		session.close();
	}
}
  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值