设计模式
简单工厂
由一个工厂对象决定创建出哪一种产品类的实例。
创建型,但不属于GOF23种设计模式。
编码风格,不属于设计模式。
# 适用场景
* 工厂类负责创建的对象比较少
* 客户端(应用层)只知道传入工厂类的参数,对于如何创建对象(逻辑)不关心
# 优点
* 只需要传入一个正确的参数,就可以获取你所需要的对象,无需知道其创建细节。
# 缺点
* 工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,违背开闭原则。
* 无法形成基于继承的结构。
Video
public abstract class Video {
public abstract void produce();
}
JavaVideo
public class JavaVideo extends Video {
@Override
public void produce() {
System.out.println("Java 开讲了!!!");
}
}
PythonVideo
public class PythonVideo extends Video {
@Override
public void produce() {
System.out.println("Python开讲了!!!");
}
}
VideoFactory
public class VideoFactory {
/**
* 重载 通过反射弥补扩展性(复杂的逻辑判断)
* @param Class c
* @return Video
*/
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;
}
/**
* 重载 工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,违背开闭原则。
* @param String type
* @return Video
*/
public Video getVideo(String type){
if("java".equalsIgnoreCase(type)){
return new JavaVideo();
}else if("python".equalsIgnoreCase(type)){
return new PythonVideo();
}
return null;
}
}
Test
public class Test {
public static void main(String[] args) {
//创建工厂
VideoFactory videoFactory = new VideoFactory();
//工厂根据传入的参数生产出对应的对象
Video video = videoFactory.getVideo(JavaVideo.class);
//Video video = videoFactory.getVideo("java");
if(video == null){
return;
}
//指定目标逻辑
video.produce();
}
}
案例: JDK 的 Calendar
private static Calendar createCalendar(TimeZone zone,
Locale aLocale){... 根据不同国家做判断}
案例: JDBC 加载启动
Class.forName("com.mysql.jdbc.Driver");
// Driver 类
package com.mysql.jdbc;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
//使用静态代码块注册了驱动:这里注册的Driver 是mysql的Driver
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
案例: logback 的 LoggerContext
public final Logger getLogger(final String name) {
Logger childLogger = (Logger) loggerCache.get(name);
}
工厂方法
定义一个创建对象的抽象方法,让子类通过继承来实现该抽象方法,但让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化过程推迟到子类中进行,也就是将创建对象的任务委托给不同的子类工厂
。
工厂方法是为了解决同一产品等级业务抽象问题。
创建型。
# 适用场景
* 创建对象需要大量重复的代码
* 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
* 一个类通过其子类来指定创建哪个对象
# 优点
* 用户只需要关心所需产品对应的工厂,无须关心创建细节
* 加入新产品符合开闭原则,提高可扩展性
# 缺点
* 类的个数容易过多,增加复杂度。(不同类型的工厂类,负责生生产不同类型的产品类)
* 增加了系统的抽象性和理解难度
Video
public abstract class Video {
//Java、Python 都属于同一个产品等级
public abstract void produce();
}
VideoFactory
public abstract class VideoFactory {
public abstract Video getVideo();
}
JavaVideo
public class JavaVideo extends Video {
@Override
public void produce() {
System.out.println("录制Java课程视频");
}
}
JavaVideoFactory
public class JavaVideoFactory extends VideoFactory {
@Override
public Video getVideo() {
return new JavaVideo();
}
}
PythonVideo
public class PythonVideo extends Video {
@Override
public void produce() {
System.out.println("录制Python课程视频");
}
}
PythonVideoFactory
public class PythonVideoFactory extends VideoFactory {
@Override
public Video getVideo() {
return new PythonVideo();
}
}
Test
public class Test {
public static void main(String[] args) {
VideoFactory videoFactory = new PythonVideoFactory();
VideoFactory videoFactory2 = new JavaVideoFactory();
//通过工厂创建具体实例
Video video = videoFactory.getVideo();
video.produce();
}
}
案例: Collection 类的 iterator() 方法
抽象方法是 Collection 类的 iterator() 方法
具体实现工厂 ArrayList 类
具体创建的产品的方法是 ArrayList 类的 iterator()
抽象产品是 Iterator<E> 接口
实际产品 Collection 的各个具体实现类 iterator() 方法 返回的 Iterator<E>
抽象工厂
抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口。
无须指定它们具体的类
创建型
# 适用场景
* 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
* 强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量重复的代码。
* 提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
# 优点
* 具体产品在应用层代码隔离,无需关系创建细节
* 将一个系列的产品族统一到一起创建
# 缺点
* 新增产品等级比较困难,规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口
* 增加了系统的抽象性和理解难度
CourseFactory
public interface CourseFactory {
Video getVideo();
Article getArticle();
}
Video
public abstract class Video {
public abstract void produce();
}
Article
public abstract class Article {
public abstract void produce();
}
JavaVideo
public class JavaVideo extends Video {
@Override
public void produce() {
System.out.println("录制Java课程视频");
}
}
JavaArticle
public class JavaArticle extends Article {
@Override
public void produce() {
System.out.println("编写Java课程手记");
}
}
JavaCourseFactory
public class JavaCourseFactory implements CourseFactory {
@Override
public Video getVideo() {
return new JavaVideo();
}
@Override
public Article getArticle() {
return new JavaArticle();
}
}
PythonVideo
public class PythonVideo extends Video {
@Override
public void produce() {
System.out.println("录制Python课程视频");
}
}
PythonArticle
public class PythonArticle extends Article {
@Override
public void produce() {
System.out.println("编写Python课程手记");
}
}
PythonCourseFactory
public class PythonCourseFactory implements CourseFactory {
@Override
public Video getVideo() {
return new PythonVideo();
}
@Override
public Article getArticle() {
return new PythonArticle();
}
}
Test
public class Test {
public static void main(String[] args) {
//创建A产品族的工厂
CourseFactory courseFactory = new JavaCourseFactory();
//获取产品 A1
Video video = courseFactory.getVideo();
//获取产品 A2
Article article = courseFactory.getArticle();
video.produce();//录制Java课程视频
article.produce();//编写Java课程手记
}
}
案例:Mysql 的 Connection、Mybatis 的 SqlSessionFactory
建造者
流程固定、顺序不固定:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
用户只需指定需要建造的类型就可以得到它们,建造过程及细节不需要知道。
创建型。
建造者模式和工厂模式的区别
建造者模式更注重于方法的调用顺序,工厂模式只关注创建产品。
建造者模式可以用于构建复杂的产品,而工厂模式创造出来的都是一样等级的产品。
建造者模式更专注于创建产品的过程,而工厂模式只要把产品创建出来就行了。
# 适用场景
* 如果一个对象有非常复杂的内部结构(很多属性)
* 想把复杂对象的创建和使用分离
# 优点
* 封装性好,创建和使用分离
* 扩展性好、建造类之间独立、一定程度上解耦
# 缺点
* 产生多余的Builder对象
* 产品内部发生变化,建造者都要修改,成本较大
Course
public class Course {
private String courseName;
private String coursePPT;
private String courseVideo;
private String courseArticle;
//question & answer
private String courseQA;
public String getCourseName() {
return courseName;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
public String getCoursePPT() {
return coursePPT;
}
public void setCoursePPT(String coursePPT) {
this.coursePPT = coursePPT;
}
public String getCourseVideo() {
return courseVideo;
}
public void setCourseVideo(String courseVideo) {
this.courseVideo = courseVideo;
}
public String getCourseArticle() {
return courseArticle;
}
public void setCourseArticle(String courseArticle) {
this.courseArticle = courseArticle;
}
public String getCourseQA() {
return courseQA;
}
public void setCourseQA(String courseQA) {
this.courseQA = courseQA;
}
@Override
public String toString() {
return "Course{" +
"courseName='" + courseName + '\'' +
", coursePPT='" + coursePPT + '\'' +
", courseVideo='" + courseVideo + '\'' +
", courseArticle='" + courseArticle + '\'' +
", courseQA='" + courseQA + '\'' +
'}';
}
}
CourseBuilder
public abstract class CourseBuilder {
public abstract void buildCourseName(String courseName);
public abstract void buildCoursePPT(String coursePPT);
public abstract void buildCourseVideo(String courseVideo);
public abstract void buildCourseArticle(String courseArticle);
public abstract void buildCourseQA(String courseQA);
public abstract Course makeCourse();
}
CourseActualBuilder
public class CourseActualBuilder 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 void buildCourseArticle(String courseArticle) {
course.setCourseArticle(courseArticle);
}
@Override
public void buildCourseQA(String courseQA) {
course.setCourseQA(courseQA);
}
@Override
public Course makeCourse() {
return course;
}
}
Coach
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){
this.courseBuilder.buildCourseName(courseName);
this.courseBuilder.buildCoursePPT(coursePPT);
this.courseBuilder.buildCourseVideo(courseVideo);
this.courseBuilder.buildCourseArticle(courseArticle);
this.courseBuilder.buildCourseQA(courseQA);
return this.courseBuilder.makeCourse();
}
}
Test
public class Test {
public static void main(String[] args) {
//课程构建类
CourseBuilder courseBuilder = new CourseActualBuilder();
//建造者
Coach coach = new Coach();
coach.setCourseBuilder(courseBuilder);
//建造者通过课程构建类完成课程的具体构建流程
Course course = coach.makeCourse("Java设计模式精讲",
"Java设计模式精讲PPT",
"Java设计模式精讲视频",
"Java设计模式精讲手记",
"Java设计模式精讲问答");
System.out.println(course);//Course{courseName='Java设计模式精讲', coursePPT='Java设计模式精讲PPT', courseVideo='Java设计模式精讲视频', courseArticle='Java设计模式精讲手记', courseQA='Java设计模式精讲问答'}
}
}
或者改进(链式调用)
Course
public class Course {
private String courseName;
private String coursePPT;
private String courseVideo;
private String courseArticle;
//question & answer
private String courseQA;
public Course(CourseBuilder courseBuilder) {
this.courseName = courseBuilder.courseName;
this.coursePPT = courseBuilder.coursePPT;
this.courseVideo = courseBuilder.courseVideo;
this.courseArticle = courseBuilder.courseArticle;
this.courseQA = courseBuilder.courseQA;
}
@Override
public String toString() {
return "Course{" +
"courseName='" + courseName + '\'' +
", coursePPT='" + coursePPT + '\'' +
", courseVideo='" + courseVideo + '\'' +
", courseArticle='" + courseArticle + '\'' +
", courseQA='" + courseQA + '\'' +
'}';
}
public static class CourseBuilder{
private String courseName;
private String coursePPT;
private String courseVideo;
private String courseArticle;
//question & answer
private String courseQA;
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 CourseBuilder buildCourseArticle(String courseArticle) {
this.courseArticle = courseArticle;
return this;
}
public CourseBuilder buildCourseQA(String courseQA) {
this.courseQA = courseQA;
return this;
}
/**
* 完成 Course 的构造
* @return
*/
public Course build(){
return new Course(this);
}
}
}
Test
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);//Course{courseName='Java设计模式精讲', coursePPT='Java设计模式精讲PPT', courseVideo='Java设计模式精讲视频', courseArticle='null', courseQA='null'}
}
}
案例:StringBuilder、StringBuffer、ImmutableSet(Guava)
Set<String> set = ImmutableSet.<String>builder().add("a").add("b").build();
System.out.println(set);//[a, b]
单例
保证一个类仅有一个实例,并提供一个全局访问点。
创建型
# 适用场景
* 想确保任何情况下都绝对只有一个实例
# 优点
* 在内存里只有一个实例,减少了内存开销
* 可以避免对资源的多重占用
* 设置了全局访问点,严格控制访问
# 缺点
* 没有接口,扩展困难(需要修改代码)
# 重点
* 私有构造函数
* 线程安全
* 延迟加载
* 序列化和反序列化安全
* 防御反射攻击
# 单例模式和其他设计模式的结合应用
* 单例模式和工厂模式
* 单例模式和享元模式
懒汉式(线程安全:性能太低 —— 同步方法)
public class LazySingleton {
//初始化时不创建(延迟加载)
private static LazySingleton lazySingleton = null;
/**
* 私有化无参构造函数
*/
private LazySingleton(){
}
/**
* 全局访问点
* @return
*/
public synchronized static LazySingleton getInstance(){
//空则创建:延迟加载
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
//非空直接返回
return lazySingleton;
}
懒汉式 (性能优化)
LazyDoubleCheckSingleton
第一种解决思路(双重检查):不允许 第二步、第三步 发生重排序
public class LazyDoubleCheckSingleton {
//不允许:第二步、第三步 (禁止重排序)
private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
private LazyDoubleCheckSingleton(){
}
public static LazyDoubleCheckSingleton getInstance(){
if(lazyDoubleCheckSingleton == null){
synchronized (LazyDoubleCheckSingleton.class){
if(lazyDoubleCheckSingleton == null){
lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
//1.分配内存给这个对象
//3.设置lazyDoubleCheckSingleton 指向刚分配的内存地址
//2.初始化对象
//intra-thread semantics
//---------------//3.设置lazyDoubleCheckSingleton 指向刚分配的内存地址
}
}
}
return lazyDoubleCheckSingleton;
}
}
第二种解决思路(静态内部类:基于类初始化的延迟加载解决方案):允许发生重排序,但是不允许其他线程看到
public class StaticInnerClassSingleton {
/**
* 静态内部类:随类加载而加载
*/
private static class InnerClass{
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
/**
* 全局访问点
* @return
*/
public static StaticInnerClassSingleton getInstance(){
return InnerClass.staticInnerClassSingleton;
}
/**
* 私有化无参构造函数,如果实例已经存在则抛出异常
*/
private StaticInnerClassSingleton(){
if(InnerClass.staticInnerClassSingleton != null){
throw new RuntimeException("单例构造器禁止反射调用");
}
}
}
饿汉式
public class HungrySingleton implements Serializable,Cloneable{
private final static HungrySingleton hungrySingleton;
//随类加载而初始化
static{
hungrySingleton = new HungrySingleton();
}
private HungrySingleton(){
if(hungrySingleton != null){
throw new RuntimeException("单例构造器禁止反射调用");
}
}
/**
* 全局访问点
* @return
*/
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
序列化破坏单例模式的解决方案
HungrySingleton
public class HungrySingleton implements Serializable,Cloneable{
private final static HungrySingleton hungrySingleton;
//随类加载而初始化
static{
hungrySingleton = new HungrySingleton();
}
private HungrySingleton(){
if(hungrySingleton != null){
throw new RuntimeException("单例构造器禁止反射调用");
}
}
/**
* 全局访问点
* @return
*/
public static HungrySingleton getInstance(){
return hungrySingleton;
}
/**
* ObjectInputStream 需要用到
* @return
*/
private Object readResolve(){
return hungrySingleton;
}
/**
*
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() throws CloneNotSupportedException {
return getInstance();
}
}
测试
//序列化
HungrySingleton instance = HungrySingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
oos.writeObject(instance);
//反序列化
File file = new File("singleton_file");
//需要用到 HungrySingleton 类的 readResolve() 方法
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
HungrySingleton newInstance = (HungrySingleton) ois.readObject();
System.out.println(instance);//序列化:HungrySingleton@9807454
System.out.println(newInstance);//反序列化:HungrySingleton@9807454
System.out.println(instance == newInstance);//true
反射攻击破坏单例模式的解决方案
懒汉式模式下无法阻止反射攻击,反射可以拿到所有成员变量做修改。
饿汉式可以避免方式攻击:通过阻止反射调用私有构造函数
HungrySingleton
public class HungrySingleton implements Serializable{
private final static HungrySingleton hungrySingleton;
//随类加载而初始化
static{
hungrySingleton = new HungrySingleton();
}
//阻止反射调用私有构造函数:阻止反射攻击
private HungrySingleton(){
if(hungrySingleton != null){
throw new RuntimeException("单例构造器禁止反射调用");
}
}
/**
* 全局访问点
* @return
*/
public static HungrySingleton getInstance(){
return hungrySingleton;
}
/**
* ObjectInputStream 需要用到
* @return
*/
private Object readResolve(){
return hungrySingleton;
}
}
测试
Class objectClass = HungrySingleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
HungrySingleton instance = HungrySingleton.getInstance();
HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
会抛出异常,防止反射攻击破坏单例模式
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.geely.design.pattern.creational.singleton.Test.main(Test.java:65)
Caused by: java.lang.RuntimeException: 单例构造器禁止反射调用
at com.geely.design.pattern.creational.singleton.HungrySingleton.<init>(HungrySingleton.java:17)
... 5 more
枚举类(最佳实践:可以防止序列化和反射攻击)
public enum EnumInstance {
INSTANCE{
protected void printTest(){
System.out.println("Geely 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;
}
}
容器单例
ContainerSingleton
public class ContainerSingleton {
private ContainerSingleton(){
}
private static Map<String,Object> singletonMap = new ConcurrentHashMap<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:空间换时间(线程间隔离,每个线程都有一个实例,并不是真正意义上的单例)
ThreadLocalInstance
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();
}
}
案例:RunTime、Desktop
原形模式
原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
不需要知道任何创建的细节,不调用构造函数。
创建型
# 适用场景
* 类初始化消耗较多资源
* new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
* 构造函数比较复杂
* 循环体中生产大量对象时
# 优点
* 原型模式性能比直接new一个对象性能高(在内存中进行二进制流拷贝)
* 简化创建过程
# 缺点
* 必须配备克隆方法
* 对克隆复杂对象或对克隆出的对象进行复杂改造时,容易引入风险
* 深拷贝、浅拷贝要运用得当
# 深克隆
引用类型指向不同的对象(用于克隆引用类型的成员变量)
# 浅克隆
可用于克隆基本类型的成员变量
public class Mail implements Cloneable{
private String name;
private String emailAddress;
private String content;
public Mail(){
System.out.println("Mail Class Constructor");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "Mail{" +
"name='" + name + '\'' +
", emailAddress='" + emailAddress + '\'' +
", content='" + content + '\'' +
'}'+super.toString();
}
@Override
protected Object clone() thro ws CloneNotSupportedException {
System.out.println("clone mail object");
return super.clone();
}
}
MailUtil
public class MailUtil {
public static void sendMail(Mail mail){
String outputContent = "向{0}同学,邮件地址:{1},邮件内容:{2}发送邮件成功";
System.out.println(MessageFormat.format(outputContent,mail.getName(),mail.getEmailAddress(),mail.getContent()));
}
public static void saveOriginMailRecord(Mail mail){
System.out.println("存储originMail记录,originMail:"+mail.getContent());
}
}
Test
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
//假设 new 一个 Mail 对象非常复杂,非常消耗性能。
Mail mail = new Mail();
mail.setContent("初始化模板");
System.out.println("初始化mail:"+mail);//初始化mail:Mail{name='null', emailAddress='null', content='初始化模板'}com.geely.design.pattern.creational.prototype.Mail@7530d0a
for(int i = 0;i < 10;i++){
//使用原始的mail进行clone
Mail mailTemp = (Mail) mail.clone();
mailTemp.setName("姓名"+i);
mailTemp.setEmailAddress("姓名"+i+"@imooc.com");
mailTemp.setContent("恭喜您,此次慕课网活动中奖了");
MailUtil.sendMail(mailTemp);
System.out.println("克隆的mailTemp:"+mailTemp);
}
MailUtil.saveOriginMailRecord(mail);//存储originMail记录,originMail:初始化模板
}
}
深拷贝(默认是浅克隆)
Pig
public class Pig implements Cloneable{
private String name;
private Date birthday;
public Pig(String name, Date birthday) {
this.name = name;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Pig pig = (Pig)super.clone();
//深克隆
pig.birthday = (Date) pig.birthday.clone();
return pig;
}
@Override
public String toString() {
return "Pig{" +
"name='" + name + '\'' +
", birthday=" + birthday +
'}'+super.toString();
}
}
Test
public class Test {
public static void main(String[] args) throws CloneNotSupportedException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Date birthday = new Date(0L);
Pig pig1 = new Pig("佩奇",birthday);
Pig pig2 = (Pig) pig1.clone();
System.out.println(pig1);
System.out.println(pig2);
pig1.getBirthday().setTime(666666666666L);
System.out.println(pig1);//Pig{name='佩奇', birthday=Sat Feb 16 09:11:06 CST 1991}com.geely.design.pattern.creational.prototype.clone.Pig@4fca772d
System.out.println(pig2);//Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}com.geely.design.pattern.creational.prototype.clone.Pig@9807454
}
}
案例:ArrayList、HashMap、CacheKey(Mybatis)
外观模式
又叫门面模式,提供了一个统一的接口,用来访问子系统中的一群接口。
外观模式定义一个高层接口,让子系统更容易使用。
结构型
# 适用场景
* 子系统越来越复杂,增加外观模式提供简单调用接口
* 构建多层系统结构,利用外观对象作为每层的入口,简化层间调用
# 优点
* 简化了调用过程,无需了解深入子系统,防止带来风险。
* 减少系统依赖、松散耦合
* 更好的划分访问层次
# 缺点
* 增加子系统、扩展子系统行为容易引入风险
* 不符合开闭原则
# 外观-相关设计模式
* 外加模式和中介者模式
* 外观模式和单例模式
* 外观模式和抽象工厂模式
PointsGift
public class PointsGift {
private String name;
public PointsGift(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
QualifyService
public class QualifyService {
public boolean isAvailable(PointsGift pointsGift){
System.out.println("校验"+pointsGift.getName()+" 积分资格通过,库存通过");
return true;
}
}
PointsPaymentService
public class PointsPaymentService {
public boolean pay(PointsGift pointsGift){
//扣减积分
System.out.println("支付"+pointsGift.getName()+" 积分成功");
return true;
}
}
ShippingService
public class ShippingService {
public String shipGift(PointsGift pointsGift){
//物流系统的对接逻辑
System.out.println(pointsGift.getName()+"进入物流系统");
String shippingOrderNo = "666";
return shippingOrderNo;
}
}
GiftExchangeService
/**
* 把多个子系统封装在一起,只对外提供一个外观(门面)类提供服务,调用者无需知道各个子系统的逻辑
*/
public class GiftExchangeService {
private QualifyService qualifyService = new QualifyService();
private PointsPaymentService pointsPaymentService = new PointsPaymentService();
private ShippingService shippingService = new ShippingService();
public void giftExchange(PointsGift pointsGift){
if(qualifyService.isAvailable(pointsGift)){
//资格校验通过
if(pointsPaymentService.pay(pointsGift)){
//如果支付积分成功
String shippingOrderNo = shippingService.shipGift(pointsGift);
System.out.println("物流系统下单成功,订单号是:"+shippingOrderNo);
}
}
}
}
Test
public class Test {
public static void main(String[] args) {
PointsGift pointsGift = new PointsGift("T恤");
GiftExchangeService giftExchangeService = new GiftExchangeService();
giftExchangeService.giftExchange(pointsGift);
}
}
测试结果
校验T恤 积分资格通过,库存通过
支付T恤 积分成功
T恤进入物流系统
物流系统下单成功,订单号是:666
案例:JdbcUtils(Spring)、Configuration(Mybatis)、RequestFacade(Tomcat)
装饰者
在不改变原有对象的基础之上,将功能附加到对象上。
提供了比继承更有弹性的替代方案(扩展原有对象功能)。
结构型
# 使用场景
* 扩展一个类的功能或给一个类添加附加职责
* 动态的给一个对象添加功能,这些功能可以再动态的撤销
# 优点
* 继承的有力补充,比继承灵活,不改变原有对象的情况下给一个对象扩展功能(动态扩展功能,运行期间决定增加的功能)
* 通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果
* 符合开闭原则
# 缺点
* 会出现更多的代码,更多的类,增加程序复杂性
* 动态装饰时,多层装饰时会更复杂
# 关联其他的设计模式
* 装饰者模式、代理模式
* 装饰者模式、适配器模式
ABattercake(被装饰者)
public abstract class ABattercake {
protected abstract String getDesc();
protected abstract int cost();
}
Battercake
public class Battercake extends ABattercake {
@Override
protected String getDesc() {
return "煎饼";
}
@Override
protected int cost() {
return 8;
}
}
AbstractDecorator(抽象装饰者)
public abstract class AbstractDecorator extends ABattercake {
private ABattercake aBattercake;
public AbstractDecorator(ABattercake aBattercake) {
this.aBattercake = aBattercake;
}
protected abstract void doSomething();
@Override
protected String getDesc() {
return this.aBattercake.getDesc();
}
@Override
protected int cost() {
return this.aBattercake.cost();
}
}
EggDecorator(鸡蛋实体装饰类)
public class EggDecorator extends AbstractDecorator {
public EggDecorator(ABattercake aBattercake) {
super(aBattercake);
}
@Override
protected void doSomething() {
}
@Override
protected String getDesc() {
return super.getDesc()+" 加一个鸡蛋";
}
@Override
protected int cost() {
return super.cost()+1;
}
}
SausageDecorator(香肠实体装饰类)
public class SausageDecorator extends AbstractDecorator{
public SausageDecorator(ABattercake aBattercake) {
super(aBattercake);
}
@Override
protected void doSomething() {
}
@Override
protected String getDesc() {
return super.getDesc()+" 加一根香肠";
}
@Override
protected int cost() {
return super.cost()+2;
}
}
Test
public class Test {
public static void main(String[] args) {
ABattercake aBattercake;
aBattercake = new Battercake();
aBattercake = new EggDecorator(aBattercake);
aBattercake = new EggDecorator(aBattercake);
aBattercake = new SausageDecorator(aBattercake);
System.out.println(aBattercake.getDesc()+" 销售价格:"+aBattercake.cost());
//煎饼 加一个鸡蛋 加一个鸡蛋 加一根香肠 销售价格:12
}
}
案例
* BufferedReader:Reader是一个抽象类,BufferedReader装饰Reader.
* Servlet:SessionRepositoryRequestWrapper.
适配器
将一个类的接口转换成客户期望的另一个接口
使原本接口不兼容的类可以一起工作
结构型
# 适用场景
* 已经存在的类,它的方法和需求不匹配时(方法结果相同或相似)
* 不是软件设计阶段考虑的设计模式,是随着软件维护,由于不同产品、不同厂家造成功能类似而接口不相同情况下的解决方案。
# 优点
* 能提高类的透明性和复用,现有的类复用但不需要改变
* 目标类和适配器类解耦,提高程序扩展性
* 符合开闭原则
# 缺点
* 适配器编写过程需要全面考虑,可能会增加系统的复杂性。
* 增加系统代码可读的难度
# 适配器-扩展
* 对象适配器
* 类适配器
方式一:类适配器模式(继承)
Adaptee
public class Adaptee {
public void adapteeRequest(){
System.out.println("被适配者的方法");
}
}
Adapter
public class Adapter extends Adaptee implements Target{
@Override
public void request() {
//...
super.adapteeRequest();
//...
}
}
ConcreteTarget
public class ConcreteTarget implements Target {
@Override
public void request() {
System.out.println("concreteTarget目标方法");
}
}
Target
public interface Target {
void request();
}
Test
public class Test {
public static void main(String[] args) {
Target target = new ConcreteTarget();
target.request();//concreteTarget目标方法
Target adapterTarget = new Adapter();
adapterTarget.request();//被适配者的方法
}
}
方式二:对象适配器模式(组合优先于继承)
Adaptee
public class Adaptee {
public void adapteeRequest(){
System.out.println("被适配者的方法");
}
}
Adapter
public class Adapter implements Target{
private Adaptee adaptee = new Adaptee();
@Override
public void request() {
//...
adaptee.adapteeRequest();
//...
}
}
ConcreteTarget
public class ConcreteTarget implements Target {
@Override
public void request() {
System.out.println("concreteTarget目标方法");
}
}
Target
public interface Target {
void request();
}
Test
public class Test {
public static void main(String[] args) {
Target target = new ConcreteTarget();
target.request();//concreteTarget目标方法
Target adapterTarget = new Adapter();
adapterTarget.request();//被适配者的方法
}
}
220交流电通过适配器变为直流电5V
AC220
public class AC220 {
public int outputAC220V(){
int output = 220;
System.out.println("输出交流电"+output+"V");
return output;
}
}
DC5
public interface DC5 {
int outputDC5V();
}
PowerAdapter
public class PowerAdapter implements DC5{
private AC220 ac220 = new AC220();
@Override
public int outputDC5V() {
int adapterInput = ac220.outputAC220V();
//变压器...
int adapterOutput = adapterInput/44;
System.out.println("使用PowerAdapter输入AC:"+adapterInput+"V"+"输出DC:"+adapterOutput+"V");
return adapterOutput;
}
}
Test
public class Test {
public static void main(String[] args) {
DC5 dc5 = new PowerAdapter();
dc5.outputDC5V();//使用PowerAdapter输入AC:220V输出DC:5V
}
}
案例:XmlAdapter(XML序列化和反序列化)、JpaVendorAdapter
享元模式
提高了减少对象数量(减少内存占用,提高性能),从而改善应用所需的对象结构的方式。
运用共享技术有效地支持大量细粒度的对象。
结构型。
# 适用场景
* 常常应用于系统底层的开发,以便解决系统的性能问题。
* 系统有大量相似对象、需要缓冲池的场景。
# 优点
* 减少对象的创建,降低内存中对象的数据量,降低系统的内存,提高效率。
* 减少内存之外的其他资源占用
# 缺点
* 关注内/外部状态、关注线程安全问题
* 使系统、程序的逻辑复杂化
# 扩展
* 内部状态:享元对象的一个属性,该属性不会随着外部的环境变化而变化。
* 外部状态:记录在享元对象的外部,不可以被共享的状态。
# 享元模式关联的其他模式
* 享元模式和代理模式
* 享元模式和单例模式
Employee
public interface Employee {
void report();
}
Manager
public class Manager implements Employee {
@Override
public void report() {
System.out.println(reportContent);
}
private String title = "部门经理"; //内部状态
private String department; //外部状态
private String reportContent;
public void setReportContent(String reportContent) {
this.reportContent = reportContent;
}
public Manager(String department) {
this.department = department;
}
}
EmployeeFactory
public class EmployeeFactory {
private static final Map<String,Employee> EMPLOYEE_MAP = new HashMap<String,Employee>();
public static Employee getManager(String department){
Manager manager = (Manager) EMPLOYEE_MAP.get(department);
if(manager == null){
manager = new Manager(department);
System.out.print("创建部门经理:"+department);
String reportContent = department+"部门汇报:此次报告的主要内容是......";
manager.setReportContent(reportContent);
System.out.println(" 创建报告:"+reportContent);
EMPLOYEE_MAP.put(department,manager);
}
return manager;
}
}
Test
public class Test {
//部门列表
private static final String departments[] = {"RD","QA","PM","BD"};
public static void main(String[] args) {
for(int i=0; i<10; i++){
String department = departments[(int)(Math.random() * departments.length)];
Manager manager = (Manager) EmployeeFactory.getManager(department);
manager.report();
}
}
}
控制台输出
创建部门经理:PM 创建报告:PM部门汇报:此次报告的主要内容是......
PM部门汇报:此次报告的主要内容是......
创建部门经理:BD 创建报告:BD部门汇报:此次报告的主要内容是......
BD部门汇报:此次报告的主要内容是......
PM部门汇报:此次报告的主要内容是......
BD部门汇报:此次报告的主要内容是......
创建部门经理:QA 创建报告:QA部门汇报:此次报告的主要内容是......
QA部门汇报:此次报告的主要内容是......
BD部门汇报:此次报告的主要内容是......
BD部门汇报:此次报告的主要内容是......
QA部门汇报:此次报告的主要内容是......
创建部门经理:RD 创建报告:RD部门汇报:此次报告的主要内容是......
RD部门汇报:此次报告的主要内容是......
QA部门汇报:此次报告的主要内容是......
案例:String、数据库连接池、Integer.valueOf(100)
源码解析
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
测试Integer.valueOf()
public class Test {
Integer a = Integer.valueOf(100);
Integer b = 100;
Integer c = Integer.valueOf(1000);
Integer d = 1000;
System.out.println("a==b:"+(a==b));//a==b:true
System.out.println("c==d:"+(c==d));//c==d:false
}
}
组合模式
将对象组合成树形结构以表示 “部分-整体” 的层次结构。
组合模式使客户端对单个对象和组合对象保持一致的方式处理。
结构型
# 适用场景
* 希望客户端可以忽略组合对象与单个对象的差异时
* 处理一个树形结构时
# 优点
* 清楚地定义分层次的复杂对象,表示对象的全部或部分层次
* 让客户端忽略了层次的差异,方便对整个层次结构进行控制
* 简化客户端代码
* 符合开闭原则
# 缺点
* 限制类型时会比较复杂
* 使设计变得更加抽象
# 组合模式-相关设计模式
* 组合模式和访问者模式
CatalogComponent
public abstract class CatalogComponent {
//CourseCatalog
public void add(CatalogComponent catalogComponent){
throw new UnsupportedOperationException("不支持添加操作");
}
//CourseCatalog
public void remove(CatalogComponent catalogComponent){
throw new UnsupportedOperationException("不支持删除操作");
}
//Course / CourseCatalog
public String getName(CatalogComponent catalogComponent){
throw new UnsupportedOperationException("不支持获取名称操作");
}
//Course
public double getPrice(CatalogComponent catalogComponent){
throw new UnsupportedOperationException("不支持获取价格操作");
}
//Course
public void print(){
throw new UnsupportedOperationException("不支持打印操作");
}
}
Course
public class Course extends CatalogComponent {
private String name;
private double price;
public Course(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public String getName(CatalogComponent catalogComponent) {
return this.name;
}
@Override
public double getPrice(CatalogComponent catalogComponent) {
return this.price;
}
@Override
public void print() {
System.out.println("Course Name:"+name+" Price:"+price);
}
}
CourseCatalog
public class CourseCatalog extends CatalogComponent {
private List<CatalogComponent> items = new ArrayList<CatalogComponent>();
private String name;
private Integer level;
public CourseCatalog(String name,Integer level) {
this.name = name;
this.level = level;
}
@Override
public void add(CatalogComponent catalogComponent) {
items.add(catalogComponent);
}
@Override
public String getName(CatalogComponent catalogComponent) {
return this.name;
}
@Override
public void remove(CatalogComponent catalogComponent) {
items.remove(catalogComponent);
}
@Override
public void print() {
System.out.println(this.name);
for(CatalogComponent catalogComponent : items){
if(this.level != null){
for(int i = 0; i < this.level; i++){
System.out.print(" ");
}
}
catalogComponent.print();
}
}
}
控制台输出
慕课网课程主目录
Course Name:Linux课程 Price:11.0
Course Name:Windows课程 Price:11.0
Java课程目录
Course Name:Java电商一期 Price:55.0
Course Name:Java电商二期 Price:66.0
Course Name:Java设计模式 Price:77.0
案例:awt.Container、HashMap.putAll()、ArrayList.addAll()
桥接模式
将抽象部分与它的具体实现部分分离,使它们都可以独立地变化(一定程度上实现解耦)。
通过组合的方式建立两个类之间联系,而不是继承。
结构型
# 适用场景
* 抽象和具体实现之间增加更多的灵活性。
* 一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立进行扩展。
* 不希望使用继承,或因为多层继承导致系统类的个数剧增。
# 优点
* 分离抽象部分及其具体实现部分
* 提高了系统的可扩展性
* 符合开闭原则
* 符合合成复用原则
* 组合优于继承
# 缺点
* 增加系统的理解与设计难度
* 需要正确地识别出系统中两个独立变化的维度
# 桥接 - 相关设计模式
* 桥接模式和组合模式
* 桥接模式和适配器模式
Account
public interface Account {
Account openAccount();
void showAccountType();
}
DepositAccount
public class DepositAccount implements Account {
@Override
public Account openAccount() {
System.out.println("打开定期账号");
return new DepositAccount();
}
@Override
public void showAccountType() {
System.out.println("这是一个定期账号");
}
}
SavingAccount
public class SavingAccount implements Account {
@Override
public Account openAccount() {
System.out.println("打开活期账号");
//...
return new SavingAccount();
}
@Override
public void showAccountType() {
System.out.println("这是一个活期账号");
}
}
Bank
public abstract class Bank {
protected Account account;
public Bank(Account account){
this.account = account;
}
abstract Account openAccount();
}
ABCBank
public class ABCBank extends Bank {
public ABCBank(Account account) {
super(account);
}
@Override
Account openAccount() {
System.out.println("打开中国农业银行账号");
account.openAccount();//委托业务实现
return account;
}
}
ICBCBank
public class ICBCBank extends Bank {
public ICBCBank(Account account) {
super(account);
}
@Override
Account openAccount() {
System.out.println("打开中国工商银行账号");
account.openAccount();//委托业务实现
return account;
}
}
Test
public class Test {
public static void main(String[] args) {
Bank icbcBank = new ICBCBank(new DepositAccount());
Account icbcAccount = icbcBank.openAccount();
icbcAccount.showAccountType();
Bank icbcBank2 = new ICBCBank(new SavingAccount());
Account icbcAccount2 = icbcBank2.openAccount();
icbcAccount2.showAccountType();
Bank abcBank = new ABCBank(new SavingAccount());
Account abcAccount = abcBank.openAccount();
abcAccount.showAccountType();
}
}
控制台输出
打开中国工商银行账号
打开定期账号
这是一个定期账号
打开中国工商银行账号
打开活期账号
这是一个活期账号
打开中国农业银行账号
打开活期账号
这是一个活期账号
案例:Driver(JDBC,mysql、oracle都实现了 Driver 接口)、
代理模式
为其他对象提供一种代理,以控制对这个对象的访问。
代理对象在客户端和目标对象之间起到中介的作用。
结构型
# 适用场景
* 保护目标对象(客户端无需于目标对象直接接触,而是和代理对象直接接触)
* 增强目标对象
# 优点
* 代理模式能将代理对象与真实被调用的目标对象分离
* 一定程度上降低了系统的耦合度,扩展性好
* 保护目标对象
* 增强目标对象
# 缺点
* 会造成系统设计中类的数目增加
* 在客户端和目标对象之间引入代理对象,会造成请求处理速度变慢
* 增加系统的复杂度
# 扩展
* 静态代理
* 动态代理(JDK动态代理只支持实现接口的类、CGLib可以代理类,但不支持final修饰的类或方法)
# Spring 代理选择
* 当 Bean 有实现接口时,Spring 就会用 JDK 的动态代理
* 当 Bean 没有实现接口时,Spring 使用 CGlib
* 可以强制使用 CGlib
在 spring 配置中加入 <aop:aspectj-autoproxy proxy-target-class="true"/>
# 代理速度对比
* CGLib:底层通过ASM字节码生成,性能高于JAVA反射。
* JDK动态代理:JAVA反射。
* JDK8 的 JDK动态代理 性能大约比 CGLib 快20%左右。
# 代理 - 相关设计模式
* 代理模式和装饰模式
* 代理模式和适配器模式