一、 简单工厂
由一个工厂对象决定创建出哪一种产品类的实例
适用:
- 工厂类负责创建的对象比较少
- 应用层只知道传入工厂类的参数,不关心如何创建对象
优点:
- 只需要传入一个正确的参数,就可以获取对象,不需要知道细节
缺点:
- 工厂类的职责相对过重,增加新产品需要修改工厂类的判断逻辑,违背开闭原则
- 无法形成基于继承的等级结构
// 简单判断
public Video getVideo(String type) {
if ("java".equalsIgnoreCase(type)) {
return new JavaVideo();
} else if ("python".equalsIgnoreCase(type)) {
return new PythonVideo();
}
return null;
}
// 基于反射 弥补开闭原则
public Video getVideo(Class c) {
Video video = null;
try {
video = (Video) Class.forName(c.getName()).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return video;
}
二、 工厂方法
定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类
工厂方法让类的实例化推迟到子类中进行
适用:
- 创建对象需要大量重复的代码
- 应用层不依赖与产品实例如何被创建等细节
- 一个类通过其子类来指定创建哪个对象
优点:
- 用户只需要关心所需产品对应的工厂,无需关心创建细节
- 加入新产品符合开闭原则,提高可扩展性
缺点:
- 类的个数容易过多,增加复杂度
- 增加类系统的抽象性和理解难度
public abstract class VideoFactory {
public abstract Video getVideo();
}
public class JavaVideoFactory extends VideoFactory {
@Override
public Video getVideo() {
return new JavaVideo();
}
}
public class PythonVideoFactory extends VideoFactory {
@Override
public Video getVideo() {
return new PythonVideo();
}
}
三、 抽象工厂
抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口
适用:
- 应用层不依赖于产品类实例如何被创建实现等细节
- 强调一系列相关的产品对象(同一产品族)一起使用创建对象需要大量重复的代码
- 提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现
优点:
- 具体产品在应用层代码隔离,无需关心创建细节
- 一个系列的产品族一起创建
缺点:
- 规定了所有可能被创建的产品集合,产品族中扩展新的产品需要修改抽象工厂的接口
- 增加系统的抽象性和复杂度
同一产品族:不同类,有一定关系,属于同一系列
同一产品等级结构:同一个类的子类,不同实现
抽象工厂关注产品族
工厂方法关注产品等级结构
public interface CourseFactory {
Video getVideo();
Article getArticle();
}
public class JavaCourseFactory implements CourseFactory {
@Override
public Video getVideo() {
return new JavaVideo();
}
@Override
public Article getArticle() {
return new JavaArticle();
}
}
public class PythonCourseFactory implements CourseFactory {
@Override
public Video getVideo() {
return new PythonVideo();
}
@Override
public Article getArticle() {
return new PythonArticle();
}
}
四、 建造者模式
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
适用:
- 对象有非常复杂的内部结构
- 想把复杂对象的创建和适用分离
优点:
- 封装性好,创建和适用分离
- 扩展性好,建造类间独立,一定程度上解耦
缺点:
- 产生多余的Builder对象
- 产品内部发生变化,建造者都要修改,成本较大
// v1
public abstract class CourseBuilder {
public abstract void buildCourseName(String courseName);
public abstract void buildCoursePPT(String coursePPT);
public abstract void buildCourseVideo(String courseVideo);
public abstract Course makeCourse();
}
public class JavaCourseBuilder extends CourseBuilder {
private Course course = new Course();
@Override
public void buildCourseName(String courseName) {
course.setCourseName(courseName);
}
@Override
public void buildCoursePPT(String coursePPT) {
course.setCoursePPT(coursePPT);
}
@Override
public void buildCourseVideo(String courseVideo) {
course.setCourseVideo(courseVideo);
}
@Override
public Course makeCourse() {
return course;
}
}
public class Coach {
private CourseBuilder courseBuilder;
public void setCourseBuilder(CourseBuilder courseBuilder) {
this.courseBuilder = courseBuilder;
}
public Course makeCourse(String courseName, String coursePPT, String courseVideo, String courseArticle,
String courseQA) {
courseBuilder.buildCourseName(courseName);
courseBuilder.buildCoursePPT(coursePPT);
courseBuilder.buildCourseVideo(courseVideo);
return courseBuilder.makeCourse();
}
}
public class Test {
public static void main(String[] args) {
CourseBuilder courseBuilder = new JavaCourseBuilder();
Coach coach = new Coach();
coach.setCourseBuilder(courseBuilder);
Course course = coach.makeCourse("Java设计模式精讲", "Java设计模式精讲PPT", "Java设计模式精讲视频");
System.out.println(course);
}
}
// v2 链式调用
public class Course {
private String courseName;
private String coursePPT;
private String courseVideo;
public Course(CourseBuilder courseBuilder) {
this.courseName = courseBuilder.courseName;
this.coursePPT = courseBuilder.coursePPT;
this.courseVideo = courseBuilder.courseVideo;
}
public static class CourseBuilder {
private String courseName;
private String coursePPT;
private String courseVideo;
public CourseBuilder buildCourseName(String courseName) {
this.courseName = courseName;
return this;
}
public CourseBuilder buildCoursePPT(String coursePPT) {
this.coursePPT = coursePPT;
return this;
}
public CourseBuilder buildCourseVideo(String courseVideo) {
this.courseVideo = courseVideo;
return this;
}
public Course build() {
return new Course(this);
}
}
}
public class Test {
public static void main(String[] args) {
Course course = new Course.CourseBuilder().buildCourseName("Java设计模式精讲").buildCoursePPT("Java设计模式精讲PPT").buildCourseVideo("Java设计模式精讲视频").build();
System.out.println(course);
}
}
五、 单例模式
保证一个类仅有一个实例,并提供一个全局访问点
适用:
- 确保任何情况下都绝对只有一个实例
优点:
- 在内存里只有一个实例,减少类内存开销
- 避免对资源的多重占用
- 设置全局访问点,严格控制访问
缺点:
- 没有接口,扩展困难
重点:
- 私有构造器
- 线程安全
- 延迟加载
- 序列化和反序列化
- 反射
懒汉式:延迟加载
// 方式1:synchronized 保证线程安全
// 缺陷:synchronized 耗费资源
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton() {
// 私有构造器 保证不会被其他类调用
}
public synchronized static LazySingleton getInstance(){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
// 方式2:volatile 防止指令重排序 + 双重检查减少锁的使用频率
// 不加 volatile:其他线程可能访问到未初始化完成的单例对象
public class LazySingleton {
private volatile static LazySingleton lazyDoubleCheckSingleton = null;
private LazySingleton(){
}
public static LazySingleton getInstance() {
if(lazyDoubleCheckSingleton == null) {
synchronized (LazySingleton.class) {
if(lazyDoubleCheckSingleton == null){
lazyDoubleCheckSingleton = new LazySingleton();
}
}
}
return lazyDoubleCheckSingleton;
}
}
// 方式3:静态内部类
// 类的初始化阶段会加锁 因此线程安全
public class LazySingleton {
private LazySingleton(){
}
// 私有
private static class Holder {
private static LazySingleton staticInnerClassSingleton = new LazySingleton();
}
public static LazySingleton getInstance(){
return Holder.staticInnerClassSingleton;
}
}
饿汉式:类加载即赋值
public class HungrySingleton {
private static HungrySingleton hungrySingleton;
static {
hungrySingleton = new HungrySingleton();
}
private HungrySingleton(){
}
public static HungrySingleton getInstance() {
return hungrySingleton;
}
}
问题1:序列化再反序列化得到的是不同的对象
// 以饿汉式为例
HungrySingleton instance = HungrySingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
oos.writeObject(instance);
File file = new File("singleton_file");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
HungrySingleton newInstance = (HungrySingleton) ois.readObject();
System.out.println(instance == newInstance); // false
解决:
public class HungrySingleton implements Serializable {
// ...
private Object readResolve(){
return hungrySingleton;
}
}
得到同一个对象instance == newInstance
,但是在过程中创建了对象。
问题2:通过反射开放构造器权限
Class objectClass = HungrySingleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
HungrySingleton instance = HungrySingleton.getInstance(); // 注意顺序
System.out.println(instance == newInstance);
解决:
// 对于 类加载时就实例化的单例实现,即饿汉式/静态内部类
private hungrySingleton() {
if(hungrySingleton != null){
throw new RuntimeException("单例构造器禁止反射调用");
}
}
// 而对于其他情况,如双重检查饿汉式,仍然会创建两个对象 无法避免
枚举:完美解决所有问题
- 天然支持序列化/反序列化
- 只有一个构造器,且不能通过反射调用
public enum EnumInstance {
INSTANCE {
@Override
protected void printTest() {
System.out.println("Print Test");
}
};
protected abstract void printTest();
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumInstance getInstance() {
return INSTANCE;
}
}
容器:统一管理多个单例,由 key 保证单一
public class ContainerSingleton {
private ContainerSingleton() {
}
private static Map<String, Object> singletonMap = new HashMap<String, Object>();
public static void putInstance(String key, Object instance) {
if (StringUtils.isNotBlank(key) && instance != null) {
if (!singletonMap.containsKey(key)) {
singletonMap.put(key, instance);
}
}
}
public static Object getInstance(String key) {
return singletonMap.get(key);
}
}
ThreadLocal:线程单例
public class ThreadLocalInstance {
private static final ThreadLocal<ThreadLocalInstance> threadLocalInstanceThreadLocal
= new ThreadLocal<ThreadLocalInstance>(){
@Override
protected ThreadLocalInstance initialValue() {
return new ThreadLocalInstance();
}
};
private ThreadLocalInstance(){
}
public static ThreadLocalInstance getInstance(){
return threadLocalInstanceThreadLocal.get();
}
}
六、 原型模式
原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,不调用构造函数
适用:
- 类初始化消耗较多资源
- new 产生一个对象需要繁琐过程(数据准备,访问权限)
- 构造函数复杂
- 循环体产生大量对象
优点:
- 性能比 new 高
- 简化创建过程
缺点:
- 必须重写 clone
- 对克隆复杂对象或克隆出的对象进行复杂改造时,容易引入风险
- 深拷贝,浅拷贝得当
public class Mail implements Cloneable {
private String name;
private String emailAddress;
private String content;
public Mail() {
System.out.println("Mail Class Constructor");
}
// get and set
@Override
protected Object clone() throws CloneNotSupportedException {
System.out.println("clone mail object");
return super.clone();
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Mail mail = new Mail();
mail.setContent("初始化模板");
System.out.println("初始化mail:" + mail);
for (int i = 0; i < 10; i++) {
Mail mailTemp = (Mail) mail.clone();
mailTemp.setName("姓名" + i);
mailTemp.setEmailAddress("姓名" + i + "@imooc.com");
mailTemp.setContent("恭喜您,此次慕课网活动中奖了");
System.out.println(mailTemp);
}
}
}