栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

设计模式(二)——创建型模式

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

设计模式(二)——创建型模式

目录

一、简单工厂模式

二、工厂方法

三、抽象工厂

四、单例模式

五、原型模式

六、建造者模式(生成器模式)


创建型设计模式:

即处理对象创建过程的设计模式,根据实际情况来使用合适的模式创建对象。创建型模式主要是将系统所需要的用到的具体类封装起来,在内部实现这些具体类的创建和结合,并对外隐藏这个过程细节。外部无法直接访问这个对象的创建和组合过程。使用者只需要关心何时、何地、由谁、怎样创建这个对象

一、简单工厂模式

对产品抽象 专门定义一个类创建其他类的实例

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 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、类初始化消耗资源较多

  1. 使用new生成一个对象需要非常烦琐的过程

  2. 构造函数比较复杂

  3. 在循环体中产生大量对象

如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。

关键代码: 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;
    }
}
​

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/877991.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号