Java设计模式(二)
文章目录
- 分类
- 创建型模式
- 单例模式
- 工厂模式
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式:
- 原型模式
- 建造者模式
uml类图的六种关系
分类
- 创建型模式:单例模式,抽象工厂模式,原型模式,建造者模式,工厂模式
- 结构型模式:适配器模式,桥接模式,装饰模式,组合模式,外观模式,享元模式,代理模式
- 行为型模式:模板方法模式,命令模式,访问者模式,迭代器模式,观察者模式,中介者模式,备忘录模式,解释器模式,状态模式,策略模式,职责链模式。
创建型模式
单例模式
介绍:
所谓的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。
比如:Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象,SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个SessionFactory就够,这就会使用到单例模式。
单例模式的八种创建方式:
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法)
- 懒汉式(线程安全,同步代码块)
- 双重检查
- 静态内部类
- 枚举
饿汉式-静态常量应用实例:
步骤如下:
1)构造器私有化
2)类的内部创建对象
3)向外暴露一个静态的公共方法
4)代码实现
package com.ming.creation.singleton.type1;/*** @Author: mei_ming* @DateTime: 2022/11/14 22:31* @Description: 单例模式--饿汉式(静态变量)*/
public class SingletonTest01 {public static void main(String[] args) {Singleton instance = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();System.out.println(instance==instance2); // trueSystem.out.println(instance.hashCode()); // 1163157884System.out.println(instance2.hashCode()); // 1163157884}}
//饿汉式(静态变量)
class Singleton{//1. 构造器私有化,外部不能 newprivate Singleton(){}//2. 本类内部创建对象实例private final static Singleton instance = new Singleton();//3. 提供一个公有的静态方法,返回实例对象public static Singleton getInstance(){return instance;}
}
优缺点:
1)优点:这种写法简单,在类加载的时候就完成实例化。避免了线程同步问题。
2)缺点:在类加载的时候就完成实例化,没有达到懒加载的效果,如果从始至终从未使用过这个实例,就会造成内存浪费。
3)这种方式基于classloader机制避免了多线程的同步问题,不过,instance在类加载时就实例化,在单例模式中大多数都是调用getInstance方法,但时导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类加载,这时候初始化instance就没有达到懒加载的效果。
4)结论:这种单例模式可用,可能会造成内存浪费
饿汉式-静态代码块应用实例:
package com.ming.creation.singleton.type2;/*** @Author: mei_ming* @DateTime: 2022/11/14 22:39* @Description: 单例模式--饿汉式(静态代码块)*/
public class SingletonTest02 {public static void main(String[] args) {Singleton instance = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();System.out.println(instance==instance2); // trueSystem.out.println(instance.hashCode()); // 1163157884System.out.println(instance2.hashCode()); // 1163157884}
}//饿汉式(静态代码块)
class Singleton{//1. 构造器私有化,外部不能 newprivate Singleton(){}//2. 本类内部创建对象实例private static Singleton instance;static { // 3. 在静态代码块中,创建单例对象instance = new Singleton();}//4. 提供一个公有的静态方法,返回实例对象public static Singleton getInstance(){return instance;}
}
优缺点:
1)和上面的方式类似,只不过将实例化的过程放在静态代码块中,也是在类加载的时候,就执行静态代码块中的代码,初始化类的实例。
2)结论:这种单例模式可用,可能会造成内存浪费
懒汉式-线程不安全应用实例:
package com.ming.creation.singleton.type3;/*** @Author: mei_ming* @DateTime: 2022/11/15 22:06* @Description: 单例模式--懒汉式(线程不安全)*/
public class SingletonTest03 {public static void main(String[] args) {System.out.println("懒汉式--线程不安全");Singleton instance = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();System.out.println(instance==instance2); // trueSystem.out.println(instance.hashCode()); // 1163157884System.out.println(instance2.hashCode()); // 1163157884}
}//懒汉式(线程不安全)
class Singleton{//1. 构造器私有化,外部不能 newprivate Singleton(){}//2. 本类内部创建对象实例private static Singleton instance;//3. 提供一个公有的静态方法,当使用到该方法是,才会创建instance,返回实例对象public static Singleton getInstance(){if(instance == null){instance = new Singleton();}return instance;}
}
优缺点:
1)起到了懒加载的效果,但是只能在单线程下使用。
2)如果在多线程下,一个线程进入了if(singleton==null)
判断语句块,还未来得及往下执行,另一个线程也通过了这个判断,这时便会产生多个实例。所以多线程下不可用。
3)结论:在实际开发中,不要使用。
懒汉式-线程安全,同步方法应用实例:
package com.ming.creation.singleton.type4;/*** @Author: mei_ming* @DateTime: 2022/11/15 22:21* @Description: 懒汉式--线程安全,同步方法*/
public class SingletonTest04 {public static void main(String[] args) {System.out.println("懒汉式--线程安全,同步方法");Singleton instance = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();System.out.println(instance==instance2); // trueSystem.out.println(instance.hashCode()); // 1163157884System.out.println(instance2.hashCode()); // 1163157884}
}//懒汉式(线程安全,同步方法)
class Singleton{//1. 构造器私有化,外部不能 newprivate Singleton(){}//2. 本类内部创建对象实例private static Singleton instance;//3. 提供一个公有的静态方法,加入同步处理的代码,解决了线程安全问题public static synchronized Singleton getInstance(){if(instance == null){instance = new Singleton();}return instance;}
}
优缺点:
1)解决了线程不安全的问题
2)效率太低,每个线程在想获得类的实例时,执行getInstance()方法都要进行同步,而其实这个方法执行一次实例化代码就行了,后面的想获得该类实例,直接return就行了。
3)结论:在实际开发中,不推荐使用。
懒汉式-线程安全,同步代码块应用实例:
//懒汉式(线程安全,同步代码块)
class Singleton{private Singleton(){}private static Singleton instance;public static Singleton getInstance(){if(instance == null){synchronized (Singleton.class){instance = new Singleton();}}return instance;}
}
优缺点:
1)这种写法,本意时想对第四种实现方式的改进,因为前面同步方法效率太低,改为同步代码块
2)但是这种同步斌不能起到线程同步的作用,和第3种实现一样,会产生多个实例。
3)结论:在实际开发中,不要使用。
双重检查应用实例:
package com.ming.creation.singleton.type6;/*** @Author: mei_ming* @DateTime: 2022/11/15 22:51* @Description: 单例模式--双重检查*/
public class SingletonTest06 {public static void main(String[] args) {System.out.println("单例模式--双重检查");Singleton instance = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();System.out.println(instance==instance2); // trueSystem.out.println(instance.hashCode()); // 1163157884System.out.println(instance2.hashCode()); // 1163157884}
}
//双重检查
class Singleton{private Singleton(){}private static volatile Singleton instance;//提供一个静态的公有方法,加入双重检查代码,解决线程安全问题,同时解决懒加载问题,同时保证效率//a,b线程同时进入第一个判断里,假设a线程进入同步块,创建完实例,就走出同步块,释放锁//b线程进入,发现已经有实例,则跳出同步块//c,d,e线程调用getInstance方法,则不用进行同步处理,直接返回a线程创建的实例public static Singleton getInstance(){if(instance == null){synchronized (Singleton.class){if(instance == null) {instance = new Singleton();}}}return instance;}
}
优缺点:
1)Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两次 if(singleton==null)
检查,这样就可以保证线程安全了。
2)这样,实例化代码只用执行一次,后面再次访问时,判断 if(singleton==null)
,直接return 实例化对象,也避免了反复进行方法同步。
3)线程安全,延迟加载,效率较高。
4)结论:在实际开发中,推荐使用。
静态内部类应用实例:
package com.ming.creation.singleton.type7;/*** @Author: mei_ming* @DateTime: 2022/11/15 22:51* @Description: 静态内部类*/
public class SingletonTest07 {public static void main(String[] args) {System.out.println("静态内部类");Singleton instance = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();System.out.println(instance==instance2); // trueSystem.out.println(instance.hashCode()); // 1163157884System.out.println(instance2.hashCode()); // 1163157884}
}
//静态内部类完成
class Singleton{private Singleton(){}//写一个静态内部类,该类中有一个静态属性 Singletonprivate static class SingletonInstance{private static final Singleton SINGLETON = new Singleton();}//提供静态的公有方法,直接返回SingletonInstance.SINGLETONpublic static Singleton getInstance(){return SingletonInstance.SINGLETON;}
}
优缺点:
1)这种方式采用了类加载的机制来保证初始化实例只有一个线程。
2)静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会加载SingletonInstance类,从而完成Singleton的实例化。
3)类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程时无法进入的。
4)线程安全,延迟加载,效率较高。
5)结论:在实际开发中,推荐使用。
枚举应用实例:
package com.ming.creation.singleton.type8;/*** @Author: mei_ming* @DateTime: 2022/11/16 22:36* @Description: 单例模式--枚举*/
public class SingletonTest08 {public static void main(String[] args) {System.out.println("枚举");Singleton instance = Singleton.SINGLETON;Singleton instance2 = Singleton.SINGLETON;System.out.println(instance==instance2); // trueSystem.out.println(instance.hashCode()); // 1163157884System.out.println(instance2.hashCode()); // 1163157884instance.sayHello();}
}//枚举
enum Singleton{SINGLETON;public void sayHello(){System.out.println("hello~");}
}
优缺点:
1)借助JDK1.5中添加的枚举来实现单例模式,不仅能避免多线程同步的问题,而且还能防止反序列化重新创建新的对象。
2)结论:推荐使用
单例模式在JDK应用的源码分析
1)在JDK中,java.lang.Runtime
就是经典的单例模式
2)代码分析
//饿汉式
public class Runtime {private static Runtime currentRuntime = new Runtime();public static Runtime getRuntime() {return currentRuntime;}private Runtime() {}
}
单例模式注意事项和细节
1)单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提供性能。
2)当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用 new
3)单例模式使用的场景:需要频繁的进行创建和销毁的对象,创建对象时耗时过多或耗费资源过多,但又经常用到的对象,工具类对象,频繁访问数据库或文件的对象(比如数据源,session工厂等)
工厂模式
简单工厂模式
基本介绍
- 简单工厂模式属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族种最简单的模式。
- 简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)
- 在软件开发中,当我们会用到大量的创建某种,某类或某批对象时,就会使用到工厂模式。
组成:
- 抽象产品 :定义了产品的规范,描述了产品的主要特性和功能。(Keyboard)
- 具体产品 :实现或者继承抽象产品的子类 ,(DellKeyboard,HPKeyboard,LenovoKeyboard)
- 具体工厂 :提供了创建产品的方法,调用者通过该方法来获取产品。(KeyboardFactory)
public interface Keyboard {void print();void input(String context);
}public class DellKeyboard implements Keyboard{@Overridepublic void print() {System.out.println("DELL键盘 print");}@Overridepublic void input(String context) {System.out.println("DELL键盘 input " + context);}
}public class HPKeyboard implements Keyboard{@Overridepublic void print() {System.out.println("HP键盘 print");}@Overridepublic void input(String context) {System.out.println("HP键盘 input " + context);}
}public class LenovoKeyboard implements Keyboard{@Overridepublic void print() {System.out.println("Lenovo键盘 print");}@Overridepublic void input(String context) {System.out.println("Lenovo键盘 input " + context);}
}//具体工厂
public class KeyboardFactory {public Keyboard getInstance(String brand){if("hp".equals(brand)){return new HPKeyboard();}else if("dell".equals(brand)){return new DellKeyboard();}else if("lenovo".equals(brand)){ //新增Lenovo类型判断return new LenovoKeyboard();}return null;}
}
测试:
public class SimpleFactoryTest {public static void main(String[] args) {Scanner scan = new Scanner(System.in);KeyboardFactory keyboardFactory = new KeyboardFactory();System.out.println("请输入你想要的品牌的键盘"); //hp,dell,lenovoString brand=scan.nextLine();Keyboard instance = keyboardFactory.getInstance(brand);instance.input("test");instance.print();}
}
简单工厂模式的优缺点:
优点:封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。
缺点:getInstance()方法利用if-else创建并返回具体的键盘实例,如果增加新的键盘子类,键盘工厂的创建方法中就要增加新的if-else。这种做法扩展性差,违背了开闭原则,也影响了可读性。
工厂方法模式
基本介绍:
工厂方法模式设计方案:定义了一个创建对象的抽象方法,由子类决定要实例化的类。
组成:
抽象工厂:提供了创建产品的接口或抽象类,调用者通过它访问具体工厂的工厂方法来创建产品。
具体工厂:主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
代码实现:
//具体产品及抽象产品同上//抽象工厂
public interface IKeyboardFactory {Keyboard getInstance();
}
//具体工厂
public class DellKeyboardFactory implements IKeyboardFactory {@Overridepublic Keyboard getInstance() {return new DellKeyboard();}
}public class HPKeyboardFactory implements IKeyboardFactory {@Overridepublic Keyboard getInstance() {return new HPKeyboard();}
}public class LenovoKeyboardFactory implements IKeyboardFactory {@Overridepublic Keyboard getInstance() {return new LenovoKeyboard();}
}
测试类:
public class FactoryMethodTest {public static void main(String[] args) {Scanner scan = new Scanner(System.in);System.out.println("请输入你想要的品牌的键盘"); //hp,dell,lenovoString brand = scan.nextLine();Keyboard instance;if ("hp".equals(brand)) {instance = new HPKeyboardFactory().getInstance();} else if ("dell".equals(brand)) {instance = new DellKeyboardFactory().getInstance();} else if ("lenovo".equals(brand)) { //新增Lenovo类型判断instance = new LenovoKeyboardFactory().getInstance();} else {throw new RuntimeException("没有该brand");}instance.input("test");instance.print();}
}
工厂方法模式的优缺点:
优点:
- 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
- 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,
- 满足开闭原则;
缺点: - 每一种品牌对应一个工厂子类,在创建具体键盘对象时,实例化不同的工厂子类。但是,如果业务涉及的子类越来越多,难道每一个子类都要对应一个工厂类吗?这样会使得系统中类的个数成倍增加,增加了代码的复杂度。
抽象工厂模式:
基本介绍:
抽象工厂模式是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
组成:
抽象工厂:提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
具体工厂:主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
抽象产品:定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系
代码实现:
public interface Keyboard {void print();
}
public class DellKeyboard implements Keyboard{@Overridepublic void print() {System.out.println("DELL键盘 print");}
}
public class HPKeyboard implements Keyboard{@Overridepublic void print() {System.out.println("HP键盘 print");}
}
public interface MainFrame {void run();
}
public class DellMainFrame implements MainFrame{@Overridepublic void run() {System.out.println("Dell 主机 run~");}
}
public class HPMainFrame implements MainFrame{@Overridepublic void run() {System.out.println("HP 主机 run~");}
}
public class DellMonitor implements Monitor{@Overridepublic void play() {System.out.println("Dell显示器 play~");}
}
public class HPMonitor implements Monitor{@Overridepublic void play() {System.out.println("HP显示器 play~");}
}//抽象工厂
public interface IFactory {MainFrame createMainFrame();Monitor createMonitor();Keyboard createKeyboard();
}
public class DellFactory implements IFactory{@Overridepublic MainFrame createMainFrame() {return new DellMainFrame();}@Overridepublic Monitor createMonitor() {return new DellMonitor();}@Overridepublic Keyboard createKeyboard() {return new DellKeyboard();}
}
public class HPFactory implements IFactory{@Overridepublic MainFrame createMainFrame() {return new HPMainFrame();}@Overridepublic Monitor createMonitor() {return new HPMonitor();}@Overridepublic Keyboard createKeyboard() {return new HPKeyboard();}
}
测试类:
public class AbsFactoryTest {public static void main(String[] args) {Scanner scan = new Scanner(System.in);System.out.println("请输入你想要的品牌"); //hp,dell,lenovoString brand = scan.nextLine();IFactory factory;if ("hp".equals(brand)) {factory = new HPFactory();} else if ("dell".equals(brand)) {factory = new DellFactory();} else {throw new RuntimeException("找不到该brand");}factory.createKeyboard().print();factory.createMainFrame().run();factory.createMonitor().play();}
}
抽象工厂模式的优缺点:
- 增加分组非常简单,例如要增加Lenovo分组,只需创建Lenovo工厂和具体的产品实现类。
- 分组中的产品扩展非常困难,要增加一个鼠标Mouse,既要创建抽象的Mouse接口, 又要增加具体的实现:DellMouse、HPMouse, 还要再每个Factory中定义创建鼠标的方法实现。
工厂模式在JDK中的运用:
Calendar类
package com.ming.creation.factory.jdksrc;import java.util.Calendar;/*** @Author: mei_ming* @DateTime: 2022/11/21 15:33* @Description: Calender中工厂模式的使用*/
public class Factory {public static void main(String[] args) {Calendar instance = Calendar.getInstance();// 注意月份下标从0开始,所以取月份要+1System.out.println("年:"+instance.get(Calendar.YEAR));System.out.println("月:"+(instance.get(Calendar.MONTH)+1));System.out.println("日:"+instance.get(Calendar.DAY_OF_MONTH));System.out.println("时:"+instance.get(Calendar.HOUR_OF_DAY));System.out.println("分:"+instance.get(Calendar.MINUTE));System.out.println("秒:"+instance.get(Calendar.SECOND));}/*** 1. 调用Calendar的getInstance()方法:* public static Calendar getInstance()* {* return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));* }** 2. 调用createCalendar()方法:** private static Calendar createCalendar(TimeZone zone,* Locale aLocale)* {* CalendarProvider provider =* LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)* .getCalendarProvider();* if (provider != null) {* try {* return provider.getInstance(zone, aLocale);* } catch (IllegalArgumentException iae) {* // fall back to the default instantiation* }* }** Calendar cal = null;** // 3. 在这里使用到简单工厂模式* if (aLocale.hasExtensions()) {* String caltype = aLocale.getUnicodeLocaleType("ca");* if (caltype != null) {* switch (caltype) {* case "buddhist":* cal = new BuddhistCalendar(zone, aLocale);* break;* case "japanese":* cal = new JapaneseImperialCalendar(zone, aLocale);* break;* case "gregory":* cal = new GregorianCalendar(zone, aLocale);* break;* }* }* }* ....* return cal;* }*/
}
工厂模式小结:
- 工厂模式的意义:将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。
- 三种工厂模式:简单工厂模式,工厂方法模式,抽象工厂模式。
- 设计模式的依赖抽象原则:
- 创建对象实例时,不要直接new 类,而是把这个new类的动作放在一个工厂的方法中,并返回。
- 不要让类继承具体类,而是继承抽象类或者实现interface(接口)
- 不要覆盖基类中已经实现的方法。
简单工厂:唯一工厂类,一个产品抽象类,工厂类的创建方法依据入参判断并创建具体产品对象。
工厂方法:多个工厂类,一个产品抽象类,利用多态创建不同的产品对象,避免了大量的if-else判断。
抽象工厂:多个工厂类,多个产品抽象类,产品子类分组,同一个工厂实现类创建同组中的不同产品,减少了工厂子类的数量。
原型模式
问题:如何实现克隆羊,要求创建5个一样的羊,名字为tom,1岁,白色。
传统方法:
public class Sheep {private String name;private int age;private String color;
}public class Client {public static void main(String[] args) {Sheep sheep = new Sheep("tom",1,"白色");Sheep sheep1 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());Sheep sheep3 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());Sheep sheep4 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());System.out.println(sheep); // Sheep{name='tom', age=1, color='白色'}System.out.println(sheep1); // Sheep{name='tom', age=1, color='白色'}System.out.println(sheep2); // Sheep{name='tom', age=1, color='白色'}System.out.println(sheep3); // Sheep{name='tom', age=1, color='白色'}System.out.println(sheep4); // Sheep{name='tom', age=1, color='白色'}}
}
缺点:
- 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低,
- 不能动态创建,不够灵活
改进思路:
Java中Object类时所有类的根类,Object类提供了一个clone()方法,该方法可以将一个Java对象复制一份,但需要实现clone的Java类必须实现一个接口Cloneable,该接口表示该类能够复制且具有复制的能力 --> 原型模式
原型模式基本介绍
- 原型模式(prototype)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。
- 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节。
- 工作原理:通过将一个原型对象传给哪个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝他们自己来实现创建,即
对象.clone()
组成
- 抽象原型类:规定了具体原型对象必须实现的的 clone() 方法。
- 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
- 访问类:使用具体原型类中的 clone() 方法来复制新的对象。
代码演示:
public class Sheep implements Cloneable{private String name;private int age;private String color;//.....//克隆该实例,使用默认的clone()方法@Overrideprotected Object clone() {Sheep sheep = null;try {sheep = (Sheep) super.clone();} catch (CloneNotSupportedException e) {System.out.println(e.getMessage());e.printStackTrace();}return sheep;}
}
测试:
public class PhotoTypeTest {public static void main(String[] args) {Sheep sheep = new Sheep("tom",1,"白色");Sheep sheep1 =(Sheep) sheep.clone();Sheep sheep2 =(Sheep) sheep.clone();Sheep sheep3 =(Sheep) sheep.clone();System.out.println(sheep); // Sheep{name='tom', age=1, color='白色'}System.out.println(sheep1); // Sheep{name='tom', age=1, color='白色'}System.out.println(sheep2); // Sheep{name='tom', age=1, color='白色'}System.out.println(sheep3); // Sheep{name='tom', age=1, color='白色'}}
}
原型模式在Spring框架的运用:
<!-- beans.xml -->
<bean id= "id01" class="com.ming.bean.Master" scope="phototype">
public class Master{private Integer id=10;private String name="孙悟空";
}
public class PhotoTypeTest {public static void main(String[] args) {ApplicationContext app = new ClassPathXmlApplicationContext("beans.xml");//获取master[通过id获取master]Object bean = app.getBean("id01");System.out.println(bean);Object bean2 = app.getBean("id01");System.out.println(bean2);System.out.println(bean==bean2); // false 说明是原型模式创建的bean2}
}
原型模式的克隆分为浅拷贝和深拷贝。
- 浅拷贝:
- 对于数据类型是基本类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象
- 对于引用类型的成员变量,仅把内存地址复制一份给新的对象,实际上两个变量都指向同一个实例。
- 深拷贝:
- 基本类型还是引用类型,都会复制创建新的对象。
- 实现方式1:重写clone()方法
- 实现方式2:通过对象序列化来实现(推荐)
问:默认情况下,如果属性是对象,拷贝时是哪一种?
答:属于浅拷贝,对象的hashcode相等。
例:
public class PhotoTypeTest {public static void main(String[] args) {Sheep sheep = new Sheep("tom",1,"白色");Sheep friend = new Sheep("jack", 1, "黑色");sheep.setFriend(friend);Sheep sheep1 =(Sheep) sheep.clone();System.out.println("sheep:"+sheep+" sheep.friend:"+sheep.getFriend().hashCode());// sheep:Sheep{name='tom', age=1, color='白色} sheep.friend:21685669System.out.println("sheep1:"+sheep1+" sheep1.friend:"+sheep1.getFriend().hashCode());// sheep1:Sheep{name='tom', age=1, color='白色} sheep1.friend:21685669//结论:默认情况下,clone()方法对于引用类型成员变量来讲,属于浅拷贝}
}
问:如何将引用类型的成员变量也转为深拷贝?
答:有2种方式,具体如下:
public class DeepCloneTarget implements Serializable,Cloneable{private static final long serialVersionUID = 1L;private String cloneName;private String cloneClass;public DeepCloneTarget(String cloneName, String cloneClass) {this.cloneName = cloneName;this.cloneClass = cloneClass;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}
public class DeepProtoType implements Serializable, Cloneable {public String name;public DeepCloneTarget deepCloneTarget; //引用类型public DeepProtoType() {super();}//深拷贝 -> 方式1@Overrideprotected Object clone() throws CloneNotSupportedException {Object deep = null;// 这里完成对基本数据类型的克隆deep = super.clone();// 对引用数据类型单独处理DeepProtoType deepProtoType = (DeepProtoType) deep;Object clone = deepCloneTarget.clone();deepProtoType.deepCloneTarget = (DeepCloneTarget) clone;return deepProtoType;}//深拷贝 -> 方式2 通过对象的序列化实现深拷贝(推荐)public Object deepClone() {//创建流对象ByteArrayOutputStream baos = null;ObjectOutputStream oos = null;ByteArrayInputStream bais = null;ObjectInputStream ois = null;try {//序列化baos = new ByteArrayOutputStream();oos = new ObjectOutputStream(baos);oos.writeObject(this); //当前这个对象以对象流的方式输出//反序列化bais = new ByteArrayInputStream(baos.toByteArray());ois = new ObjectInputStream(bais);DeepProtoType copyObj = (DeepProtoType)ois.readObject();return copyObj;} catch (IOException e) {e.printStackTrace();return null;} catch (ClassNotFoundException e) {e.printStackTrace();return null;}finally {//关闭流try {baos.close();oos.close();bais.close();ois.close();} catch (IOException e) {System.out.println(e.getMessage());}}}
}
测试类:
public class DeepProtoTypeTest {public static void main(String[] args) throws CloneNotSupportedException {DeepProtoType p = new DeepProtoType();p.name="rose";p.deepCloneTarget=new DeepCloneTarget("name1","class1");//方式1完成深拷贝DeepProtoType p2=(DeepProtoType)p.clone();System.out.println("p.name: "+p.name+" p.deepCloneTarget: "+ p.deepCloneTarget.hashCode());System.out.println("p2.name: "+p2.name+" p2.deepCloneTarget: "+ p2.deepCloneTarget.hashCode());
// p.name: rose p.deepCloneTarget: 21685669
// p2.name: rose p2.deepCloneTarget: 2133927002System.out.println("--------------------");//方式2实现深拷贝DeepProtoType p3=(DeepProtoType)p.deepClone();System.out.println("p.name: "+p.name+" p.deepCloneTarget: "+ p.deepCloneTarget.hashCode());System.out.println("p3.name: "+p3.name+" p3.deepCloneTarget: "+ p3.deepCloneTarget.hashCode());
// p.name: rose p.deepCloneTarget: 21685669
// p3.name: rose p3.deepCloneTarget: 1915910607}
}
原型模式的注意事项和细节:
- 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
- 不用重新初始化对象,而是动态地获取对象运行时的状态
- 如果原始对象发生变化(增加属性),其他克隆对象的也会发生相应的变化,无需修改代码
- 在实现深拷贝的时候可能需要比较复杂的代码
- 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则。
建造者模式
盖房子需求
- 需要建房子:过程为打桩,砌墙,封顶
- 房子有各种各样的,比如普通房,高楼,别墅,
- 建房子过程虽然一样,但每一步有所变化。
传统方法实现:
public abstract class AbstractHouse {//打地基public abstract void buildBasic();//砌墙public abstract void buildWalls();//封顶public abstract void roofed();//组装public void build(){buildBasic();buildWalls();roofed();}
}public class CommonHouse extends AbstractHouse{@Overridepublic void buildBasic() {System.out.println("普通房子打地基-5m");}@Overridepublic void buildWalls() {System.out.println("普通房子砌墙-10cm");}@Overridepublic void roofed() {System.out.println("普通房子封顶-水泥顶");}
}
测试:
public class Client {public static void main(String[] args) {CommonHouse commonHouse = new CommonHouse();commonHouse.build();}
}
传统方式的优缺点:
优点:比较好理解,简单易操作
缺点:没有设计缓存层对象,程序的扩展和维护不好,即,把产品和创建产品的过程封装在一起,耦合性增强了。
解决方案:将产品和产品创建过程解耦–> 建造者模式
建造者模式基本介绍:
- 建造者模式(Builder Pattern)又叫生成器模式,是一种创建型模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
- 由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
- 建造者模式是一步一步创建一个复杂对象,它允许用户只通过指定复杂对象的类型和内容就可以构建他们,用户不需要知道内部的具体构建细节。
组成
- 抽象建造者类(Builder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。
- 具体建造者类(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
- 产品类(Product):要创建的复杂对象。
- 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建,隔离了客户与对象的生产过程。
代码说明:
// 抽象建造者
public abstract class HouseBuilder {protected House house =new House();public abstract void buildBasic();public abstract void buildWalls();public abstract void roofed();//建造房子,返回public House buildHouse(){return house;}
}// 具体建造者
public class CommonHouse extends HouseBuilder{@Overridepublic void buildBasic() {house.setBasic("5m");System.out.println("普通房子打地基-5m");}@Overridepublic void buildWalls() {house.setWall("10cm");System.out.println("普通房子砌墙-10cm");}@Overridepublic void roofed() {house.setRoofed("水泥顶");System.out.println("普通房子封顶-水泥顶");}
}// 具体建造者2
public class HighHouse extends HouseBuilder{@Overridepublic void buildBasic() {house.setBasic("50m");System.out.println("高楼打地基-50m");}@Overridepublic void buildWalls() {house.setWall("20cm");System.out.println("高楼砌墙-20cm");}@Overridepublic void roofed() {house.setRoofed("玻璃");System.out.println("高楼封顶-玻璃");}
}// 产品
public class House {private String basic;private String wall;private String roofed;public String getBasic() {return basic;}public void setBasic(String basic) {this.basic = basic;}public String getWall() {return wall;}public void setWall(String wall) {this.wall = wall;}public String getRoofed() {return roofed;}public void setRoofed(String roofed) {this.roofed = roofed;}
}// 指挥者
public class HouseDirector {HouseBuilder houseBuilder = null;//构造器传入 houseBuilderpublic HouseDirector(HouseBuilder houseBuilder){this.houseBuilder=houseBuilder;}//通过setter, 传入houseBuilderpublic void setHouseBuilder(HouseBuilder houseBuilder) {this.houseBuilder = houseBuilder;}//如何处理建造房子的流程,交给指挥者public House constructHouse(){houseBuilder.buildBasic();houseBuilder.buildWalls();houseBuilder.roofed();House house = houseBuilder.buildHouse();return house;}
}
测试:
public class Client {public static void main(String[] args) {//改普通房子CommonHouse commonHouse = new CommonHouse();//准备房子的指挥者HouseDirector houseDirector = new HouseDirector(commonHouse);//调用指挥者中盖房子的流程方法,获取房子House house = houseDirector.constructHouse();System.out.println(house.getBasic());System.out.println(house.getWall());System.out.println(house.getRoofed());//通过setter 传入新的房子类型houseDirector.setHouseBuilder(new HighHouse());House house2 = houseDirector.constructHouse();System.out.println(house2.getBasic());System.out.println(house2.getWall());System.out.println(house2.getRoofed());}
}
建造者模式的优缺点:
优点:
- 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
- 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
- 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
- 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。
缺点:
- 造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
建造者模式在JDK的应用和源码分析
- java.lang.StringBuilder中的建造者模式
package com.ming.creation.builder.jdksrc;/*** @Author: mei_ming* @DateTime: 2022/11/24 22:44* @Description: StringBuilder源码 与 建造者模式*/
public class Builder {public static void main(String[] args) {StringBuilder stringBuilder = new StringBuilder();stringBuilder.append("123");System.out.println(stringBuilder);/*** 1. StringBuilder 既充当了指挥者,同时还是具体建造者,* 建造方法的实现是由 AbstractStringBuilder 完成,而 StringBuilder 继承了 AbstractStringBuilder* public final class StringBuilder extends AbstractStringBuilder* implements java.io.Serializable, CharSequence{}** 2. AbstractStringBuilder 实现了 Appendable, 是具体建造者* abstract class AbstractStringBuilder* implements Appendable, CharSequence {}** 3. Appendable 是接口,定义了多个append(),是抽象建造者* public interface Appendable {}*/}
}
创建者模式对比
工厂方法模式VS建造者模式
工厂方法模式注重的是整体对象的创建方式;而建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。
我们举个简单例子来说明两者的差异,如要制造一个超人,如果使用工厂方法模式,直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人;而如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。抽象工厂模式VS建造者模式
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。
建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。
Java设计模式(二)相关推荐
- java设计模式 (二) 创建模式
java设计模式 (二) 创建型模式 单例模式 Singleton pattern 现实场景 双11, 小华收快递, 早上圆通快递叫他下楼收快递, 刚上来, 顺丰快递叫他下楼收快递,.然后没多久EMS ...
- Java设计模式(二) -- 单例模式
单例模式是Java中最广泛应用的设计模式之一,为创建对象提供了一种绝佳的方式.因此,在一些Java程序中, 一些管理器和控制器经常被设计为单例模式. 这种模式涉及到一个单一的类,该类负责创建自己的对象 ...
- Java设计模式(二十二):原型设计模式
1. 应用场景 如果一个对象的创建总是由几种固定组件不同方式组合而成; 如果对象之间仅仅实例属性不同.将不同情况的对象缓存起来,直接克隆使用.也许这比采用传递参数重新 new 一个对象要来的快一些与工 ...
- Java设计模式(二十一):备忘录设计模式
1. 应用场景 备忘录模式经常可以遇到,譬如下面这些场景: 浏览器回退:浏览器一般有浏览记录,当我们在一个网页上点击几次链接之后,可在左上角点击左箭头回退到上一次的页面,然后也可以点击右箭头重新回到当 ...
- Java设计模式(二十):中介者设计模式
1.应用场景 世界上存在着各种各样的数据库,不同数据库有各自的应用场景,对于同一份数据,最开始可能使用关系型数据库(如MySQL)进行存储查询,使用Redis作为缓存数据库,当数据量较大时使用MySQ ...
- Java设计模式(二):观察者设计模式
1. 应用场景 某个实例的变化将影响其他多个对象. 观察者模式多用于实现订阅功能的场景,例如微博的订阅,当我们订阅了某个人的微博账号,当这个人发布了新的消息,就会通知我们. 2.概念 定义对象之间的一 ...
- Java设计模式(二十三):访问者设计模式
1. 应用场景 对于系统中的某些对象,它们存储在同一个集合中,且具有不同的类型,而且对于该集合中的对象,可以接受一类称为访问者的对象来访问,而且不同的访问者其访问方式有所不同,访问者模式为解决这类问题 ...
- Java设计模式(十二) 策略模式
策略模式介绍 策略模式定义 策略模式(Strategy Pattern),将各种算法封装到具体的类中,作为一个抽象策略类的子类,使得它们可以互换.客户端可以自行决定使用哪种算法. 策略模式类图 策略模 ...
- Java 设计模式之工厂模式(二)
原文地址:Java 设计模式之工厂模式(二) 博客地址:http://www.extlight.com 一.背景 本篇内容是 Java 设计模式创建型模式的第二篇.上一篇主题为 <Java 设计 ...
最新文章
- grailsgroovy的IllegalArgument异常
- Linux学习路径(小白必看)
- android+完美的列表,android完美讲义.pdf
- [Cocoa]深入浅出Cocoa之Core Data(2)- 手动编写代码
- 多媒体计算机辅助教学与课件制作,清华大学出版社-图书详情-《计算机辅助教学多媒体课件设计制作与应用》...
- UGUI——基本组件
- 唏嘘!又一家手机工厂关闭,一代机皇彻底退出中国制造
- 计算机设计大赛二等奖,学部在第十一届中国大学生计算机设计大赛中喜获二等奖...
- clone oracle ebs
- 40岁才博士毕业,新任安徽省长的他写了篇句句戳心的博士论文后记
- leetcode---1728. 猫和老鼠 II
- 安装hustoj的一些心得及html的笔记
- 这是历史上程序员被黑的最惨的一次,原谅我发出了杀猪般的笑声!
- 计算机创造奇迹的英语作文,大学英语作文:创造奇迹-Creating-Miracle.docx
- 大漠穷秋:如何快速构建一款SCRM小程序?
- html5移动开发是什么意思,移动端什么意思?
- java mongodb gridfs_MongoDB-4 GridFS 文件存储
- 计算机图形学——区域填充算法
- 中心极限定理与大数定理理解
- 64位程序使用ado连接oracle,32位和64位C++程序使用ADO访问ORACLE注意事项和区别