目录
一、简单工厂模式
二、工厂方法
三、抽象工厂
四、单例模式
五、原型模式
六、建造者模式(生成器模式)
创建型设计模式:
即处理对象创建过程的设计模式,根据实际情况来使用合适的模式创建对象。创建型模式主要是将系统所需要的用到的具体类封装起来,在内部实现这些具体类的创建和结合,并对外隐藏这个过程细节。外部无法直接访问这个对象的创建和组合过程。使用者只需要关心何时、何地、由谁、怎样创建这个对象
一、简单工厂模式
对产品抽象 专门定义一个类创建其他类的实例
java中的源码体现 Calendar
优点:
1、工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品;
2、简单工厂模式通过这种做法实现了对责任的分割,它提供了专门的工厂类用于创建对象。
3、客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以减少使用者的记忆量。
4、一个工厂产生一类产品,无需知道如何创建和组织的。
缺点:
1、由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。
2、使用简单工厂模式将会增加系统中类的个数,在一定程序上增加了系统的复杂度和理解难度。
3、系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。
4、简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构
例子
案例的目标:需要画出不同的图案(圆形,方形。。。。)
分析:我们首先定义一个接口描述我们的共有方法 draw() ,定义我们的产品类实现其接口
当然我们可以通过new 实现了接口的方法,去调用,但是我们需要知道所有的创建实现类的名称去调用它的draw()
所以我们可以创建一个工厂类,去实现我们的创建我们需要的产品类,只需记得产品的名就可以对其实现创建
这个就是我们的工厂
public class ShapeFactory {
//使用 getShape 方法获取形状类型的对象
public Shape getShape(String shapeType){
if(shapeType == null){
return null;
}
//
//
if(shapeType.equalsIgnoreCase("CIRCLE")){
//......
return new Circle();
} else if(shapeType.equalsIgnoreCase("RECTANGLE")){
return new Rectangle();
} else if(shapeType.equalsIgnoreCase("SQUARE")){
return new Square();
}
return null;
}
}
扩展:这样做的缺点,每次增加一个产品都需要改写源代码,在工厂中添加新的判断条件
解决办法:通过反射创建产品 这种方法也存在(异常)的问题,
升级:可以传入class的实例对象进去,进行创建
用另一个例子解释上述的升级
public class CourseFactory {
//简单工厂 实现方案一: 通过 if...else判断完成
// public ICourse create(String name){
// if("java".equals(name)){
// return new JavaCourse();
// }else if("python".equals(name)){
// return new PythonCourse();
// }else {
// return null;
// }
// }
// //简单工厂 实现方案二: 通过 反射 判断对象创建
public ICourse create(String className){
try {
if (!(null == className || "".equals(className))) {
return (ICourse) Class.forName(className).newInstance();
}
}catch (Exception e){
e.printStackTrace();
}
return null;
}
// //简单工厂 实现方案三: 通过 class对象完成对象创建.
public ICourse create(Class extends ICourse> clazz){
try {
if (null != clazz) {
return clazz.newInstance();
}
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
二、工厂方法
抽象产品和工厂
优点: (1)用工厂方法在一个类的内部创建对象,通常比直接创建对象更灵活。 (2)实现了开闭原则可以在不改变工厂的前提下,增加新产品。 (3)工厂方法模式通过面向对象的手法,将所要创建的具体对象的创建工作,延迟到了子类, 从而提供了一种扩展的策略,较好的解决了这种紧耦合的关系
缺点:系统中的类是成对增加,增加了系统的复杂度和理解度
jdk源码中的应用: 1. Collection.iterator 方法
Collection接口是抽象工厂类,ArrayList是具体的工厂类。
Iterator接口是抽象商品类,ArrayList类中的Iter内部类是具体的商品类。
具体的工厂类中iterator()方法创建具体的商品类的对象。
Collection可以看作是一个总的抽象工厂,它的一些实现这个接口的类,像ArrayList,LinkedHashSet等等可以看作一个个不同的品牌的工厂,而总的产品Iterator接口里面会定义产品所需功能的细节,然后在交给各个品牌不同的工厂来实现。
接下来通过实例了解:
左手的图是工厂的抽象类 右手是对产品的抽象
public class FactoryMethodTest {
public static void main(String[] args) {
ICourseFactory factory = new PythonCourseFactory();
ICourse course = factory.create();
course.record();
factory = new JavaCourseFactory();
course = factory.create();
course.record();
}
}
三、抽象工厂
抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。
何时使用:系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
通俗的讲就是在工厂方法的前提下添加了一个抽象工厂(工厂的工厂)
优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点:产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。
源码中的典型例子:
JDk中的 java.sql.Connection: jdk中连接数据库的代码是典型的抽象工厂模式,每一种数据库只需提供一个统一的接口: Driver-> Connection 是一个经典的抽象工厂,而 Statement、PreparedStatement、CallableStatement 是 Connection 这个抽象工厂中提供的三个抽象产品 数据库Driver 起到 Client 的作用,我们只需要把 Driver 注册进 DriverManager,就可以生成需要的 Connection。每次操作数据库只需要使用 Java 提供的这套接口就可以,不需要考虑使用的是什么 SQL 数据库 这些抽象工厂与抽象产品均由对应的数据库驱动实现,下面以 MySQL 与 Oracle 的驱动进行举例。
Connection就是那个超级工厂,在它里面负责产生 子工厂:Statement, PreparedStatement, CallableStatement工厂.
前提: SPI
DriverManager.getConnection() -> 时根据类加载时机. 执行
DriverManager中的 static块. 加载mysql驱动 . 即 mysql中的Driver类,它实现了 Driver接口.
再getConnection()得到 mysql中的 ConnectionImpl类的对象.
实例
四、单例模式
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。构造方法私有 访问实例公有
分为了饿汉式单例和懒汉式单例:饿汉式单例如果这个单例非常的大,会导致程序启动缓慢。
饿汉式单例:
它是在类加载的时候就立即初始化,并且创建单例对象
优点:没有加任何的锁、执行效率比较高, 在用户体验上来说,比懒汉式更好
缺点:类加载的时候就初始化,不管你用还是不用,我都占着空间 浪费了内存,有可能占着茅坑不拉屎
绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题
注意: 1、单例类只能有一个实例。 2、单例类必须自己创建自己的唯一实例。 3、单例类必须给所有其他对象提供这一实例。
介绍
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。 主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
应用实例: 1、 Spring ApplicationContext 2、 Spring依赖注入Bean实例默认是单例的。 DefaultSingletonBeanRegistry中的getSingleton()方法 3、 Mybatis ErrorContext ThreadLocal 4、 java.awt.DeskTop 类允许一个Java应用程序启动本地的另一个应用程序去处理URI或文件请求。 其使用了单例模式中的懒汉式, 而且是容器单例模式 5、 JDK Runtime 饿汉单例 java.util.logging.LogManager作为全局日志管理器负责维护日志配置和日志继承结构, 使用了单例模式中的饿汉式
优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。 2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景: 1、要求生产唯一序列号。 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
下面是单例的升级过程
饿汉式单例:
public class HungrySingleton1 {
private static final HungrySingleton1 hungrySingleton = new HungrySingleton1();
private HungrySingleton1(){}
public static HungrySingleton1 getInstance(){
return hungrySingleton;
}
}
public class HungryStaticSingleton2 {
//以final保证可见
private static final HungryStaticSingleton2 hungrySingleton;
static {
hungrySingleton = new HungryStaticSingleton2();
}
private HungryStaticSingleton2(){}
public static HungryStaticSingleton2 getInstance(){
return hungrySingleton;
}
}
//懒汉式单例就是在调用的时候创建对象
public class LazySimpleSingleton1 {
//这种直接用synchronized保证安全的机制性能太低了
private LazySimpleSingleton1(){}
//静态块,公共内存区域
private static LazySimpleSingleton1 lazy = null;
public synchronized static LazySimpleSingleton1 getInstance(){
if(lazy == null){
lazy = new LazySimpleSingleton1();
}
return lazy;
}
//下面的使用了双重检查所的机制,加上注 volatile 来保证多线程下的单例安全 性能进一步提高
public class LazyDoubleCheckSingleton2 {
//注意volatile:内存可见性.
private volatile static LazyDoubleCheckSingleton2 lazy = null;
private LazyDoubleCheckSingleton2(){}
public static LazyDoubleCheckSingleton2 getInstance(){
//为什么要双重检查 lazy==null
if(lazy == null){
//在些阻塞并不是基于整 个LazySimpleSingleton2的阻塞,而是在getInstance内部的阻塞,。
synchronized (LazyDoubleCheckSingleton2.class){ //性能上稍逊
if(lazy == null){
lazy = new LazyDoubleCheckSingleton2();
//1.分配内存给这个对象
//2.初始化对象
//3.设置lazy指向刚分配的内存地址
//4.初次访问对象
}
}
}
return lazy;
}
}
对与上面的单例是真正的单例嘛? 不是的 我们还可以通过用反射来破坏它的单例
//破坏单例
Class cls=LazyInnerClassSingleton3.class;
//获取构造方法
Constructor con=cls.getDeclaredConstructor(null);
//修改访问权限
con.setAccessible(true);
//初始化
Object o1=con.newInstance();
Object o2=con.newInstance();
这样我们修改它的构造方法的访问权限(构造器改为public),然后对其实现创建实例
可以用以下方法防止:在构造器中加一层判断
private LazyInnerClassSingleton(){
if( LazyHolder.LAZY!=null){
throw new RuntimeException("不允许创建多个实例");
}
}
这样就没有办法破坏单例了吗?
可以用反射序列化和反序列化实现对单例的破坏
public static void main(String[] args) {
SeriableSingleton s1=null;
SeriableSingleton s2=SeriableSingleton.getInstance();
try(FileOutputStream fos=new FileOutputStream( "seriableSingleton.obj" );
ObjectOutputStream oos=new ObjectOutputStream( fos ) ){
oos.writeObject( s2 );
oos.flush();
}catch (Exception ex){
ex.printStackTrace();
}
System.out.println("序列化完成");
try(
FileInputStream fis=new FileInputStream( "seriableSingleton.obj");
ObjectInputStream ois=new ObjectInputStream( fis );
){
s1=(SeriableSingleton)ois.readObject();
}catch(Exception ex){
ex.printStackTrace();
}
System.out.println( s1+"t"+s2);
}
解决方法:
public class SeriableSingleton implements Serializable {
//序列化就是说把内存中的状态通过转换成字节码的形式
//从而转换一个IO流,写入到其他地方(可以是磁盘、网络IO)
//将内存中状态给永久保存下来了
//反序列化: 将已经持久化的字节码内容,转换为IO流
//通过IO流的读取,进而将读取的内容转换为Java对象, 在转换过程中会重新创建对象new
//SeriableSingleton,会先初始化内部类
//如果没使用的话,内部类是不加载的
private SeriableSingleton(){
if( LazyHolder.LAZY!=null){
throw new RuntimeException("不允许创建多个实例");
}
}
//static 是为了使单例的空间共享保证这个方法不会被重写,重载
public static final SeriableSingleton getInstance(){
//在返回结果以前,一定会先加载内部类
return LazyHolder.LAZY;
}
//默认不加载
private static class LazyHolder{
//因为加了final , 保证了
// 当构造函数结束时,final类型的值是被保证其他线程访问该对象时,它们的值是可见的
private static final SeriableSingleton LAZY = new SeriableSingleton();
}
//解决方案: 增加一个 readResolve()方法即可.
private Object readResolve(){
return LazyHolder.LAZY;
}
}
这种防止反序列化的方法实际上只反了他最后的调用,在虚拟机中还是创建了一个新的这个对象。可以看看源码
最后一种单例:
注册式单例: ContainerSingleton: 容器式单例 spring的方案 EnumSingleton: 枚举常量式单例
public enum EnumSingleton1 {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton1 getInstance(){
return INSTANCE;
}
}
//利用 jad工具反向编译 EnumSingleton1的 字节码.
五、原型模式
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。 我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
介绍
意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。 主要解决:在运行期建立和删除原型。
何时使用: 1、类初始化消耗资源较多
-
使用new生成一个对象需要非常烦琐的过程
-
构造函数比较复杂
-
在循环体中产生大量对象
如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。
关键代码: 1、实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。 2、原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。
应用实例: 1、JAVA 中的 Object clone() 方法。
优点: 1、性能提高。 2、逃避构造函数的约束。
缺点: 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易, 特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。
深复制把要复制的对象所引用的对象都复制了一遍。 拷贝需要实现Cloneable, Serializable两个接口,重写clone方法
***Java 默认的 clone 方法是浅拷贝,那如何实现深拷贝呢? 1. 实现 Cloneable 接口,递归 clone 引用对象或 new 新对象(类的属性字段未实现 Cloneable 接口) 2. 借助序列化完成深拷贝,如实现 JDK java.io.Serializable 接口、json格式序列化、xml格式序列化等
在我们平时开发中,也会使用 Spring 中 org.springframework.beans.BeanUtils 的 copyProperties 方法复制一个对象的属性到另一个对象 public static void copyProperties(Object source, Object target) throws BeansException { copyProperties(source, target, null, (String[]) null); }
apache commons-beanutils 包中 org.apache.commons.beanutils.BeanUtils 工具类中有 cloneBean 方法无需实现 Cloneable 接口的浅拷贝,也有 copyProperties 和 copyPropertie 方法复制对象属性和指定属性进行复制。
注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。 浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。
例子一、
这个例子主要区别深浅克隆的区别
public class JinGuBang implements Serializable {
public float h = 100;
public float d = 10;
public void big(){
this.d *= 2;
this.h *= 2;
}
public void small(){
this.d /= 2;
this.h /= 2;
}
@Override
public String toString() {
return "JinGuBang{" +
"h=" + h +
", d=" + d +
'}';
}
}
/
public Object deepClone(){
try{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
oos.flush();
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
QiTianDaSheng copy = (QiTianDaSheng)ois.readObject();
copy.birthday = new Date();
return copy;
}catch (Exception e){
e.printStackTrace();
return null;
}
}
//浅克隆
public QiTianDaSheng shallowClone(QiTianDaSheng target){
QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();
qiTianDaSheng.height = target.height;
qiTianDaSheng.weight = target.weight;
//浅克隆对对象类型的数据只克隆了地址,没有复制值
qiTianDaSheng.jinGuBang = target.jinGuBang; //对于引用型的数据,只克隆了地址.
qiTianDaSheng.birthday = new Date();
return qiTianDaSheng;
}
@Override
public String toString() {
return "QiTianDaSheng{" +
"jinGuBang=" + jinGuBang +
", height=" + height +
", weight=" + weight +
", birthday=" + birthday +
'}';
}
}
浅克隆:八大基本数据和String会新建 其他的引用类对象会使用同一个
深克隆:全部都会新建
六、建造者模式(生成器模式)
建造者模式(Builder Pattern)使用多个简单的对象一步一步(顺序构建)构建成一个 复杂的对象。 这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。
介绍
意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。 主要解决:主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化, 这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
何时使用:一些基本部件不会变,而其组合经常变化的时候。
如何解决:将变与不变分离开。
关键代码:建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系。
应用实例: 1、JAVA 中的 StringBuilder 2、JDk中的 DocumentBuilder(org.w3c.dom)
优点: 1、建造者独立,易扩展。 2、便于控制细节风险。
缺点: 1、产品必须有共同点,范围有限制。 2、如内部变化复杂,会有很多的建造类。
使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。
注意事项:与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。
例子一:
通过抽象的创建者的抽象类ActorBuilder的子类HeroBuilder和AngelBuilder建造者对一个actor进行构建
public class ActorController {
//逐步构建复杂产品对象 规定了构建产品的顺序
public Actor construct(ActorBuilder actorBuilder) {
Actor actor;
actorBuilder.buildType();
actorBuilder.buildSex();
actorBuilder.buildFace();
actorBuilder.buildCostume();
actor = actorBuilder.createActor();
return actor;
}
}



