具体的设计模式(一):创建型模式

创建型模式

有5种:工厂方法,抽象工厂,单例模式,建造者模式,原型模式。

1.工厂方法(FactoryMethod)
简单说明:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

举例说明:
设计一个sender接口(发送接口)

package factorymethod;

public interface Sender {
    //发消息的接口
    public void send();
}

MailSender 类实现了Sender接口,意为发送邮件

package factorymethod;

public class MailSender implements Sender{
    //发邮件
    @Override
    public void send() {
        // TODO 自动生成的方法存根
        System.out.println("this is mailsender!");
    }

}

SmsSender类实现了Sender接口,意为发送短信

package factorymethod;

public class SmsSender implements Sender{
    //发短信
    @Override
    public void send() {
        // TODO 自动生成的方法存根
        System.out.println("this is sms sender!");
    }

}

创建发送工厂类:有3中方法(1、传递字符串,2、创建多个工厂方法,3、创建静态工厂方法)

package factorymethod;

public class SendFactory {

    //创建发送工厂类  (普通工厂方法,若传递的字符串出错,则不能正确创建对象)
    public Sender produce(String type) {
        if("mail".equals(type)){
            return new MailSender();  //创建Mail实例
        }else if("sms".equals(type)){
            return new SmsSender();
        }else{
            System.out.println("请输入正确的类型!");
            return null;
        }

    }

    //多个工厂方法模式
    public Sender produceMail(){
        return new MailSender();
    }

/*  public Sender produceSms(){
        return new SmsSender();
    }*/

    //静态工厂方法模式
    public static Sender produceSms(){
        return new SmsSender();
    }
}   
package factorymethod;

/**
 * 工厂方法的测试,构造型设计模式,创建实例
 * @author cx
 *
 */

public class FactoryTest {

    public static void main(String[] args) {
        SendFactory factory=new SendFactory();
        Sender sender=factory.produce("sms");  //普通工厂方法,传递的字符串可能出错
        Sender sender2=factory.produceMail();     //多个工厂方法
        Sender sender3=SendFactory.produceSms();  //静态工厂方法,无需创建实例
        sender.send();   //该sender 为smsSender 
        sender2.send();  //该sender 为mailSender
        sender3.send();  //该sender 为smsSender
    }
}

测试结果

this is sms sender!
this is mailsender!
this is sms sender!

2.抽象工厂(AbstractFactory)

简单说明:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

举例说明:
设计一个发送接口:Sender

package abstractfactory;

public interface Sender {
    public void send();
}

设计一个Provider接口:是为了让工厂变得抽象,符合依赖倒置原则,依赖抽象,而不依赖细节

package abstractfactory;

public interface Provider {

    public Sender produce();

}

MailSender类实现发送Sender接口,发送邮件

package abstractfactory;

public class MailSender implements Sender{
    //发邮件
    @Override
    public void send() {
        // TODO 自动生成的方法存根
        System.out.println("this is mailsender!");
    }

}

MonerSender类实现Sender接口,发钱

package abstractfactory;

public class MoneySender implements Sender{

    @Override
    public void send() {
        // TODO 自动生成的方法存根
        System.out.println("this is MoneySender!");
    }

}

SendMailFactory 类实现Provider接口,创建MailSender()实例

package abstractfactory;

public class SendMailFactory implements Provider{

    @Override
    public Sender produce() {
        // TODO 自动生成的方法存根
        return new MailSender();
    }

}

SendMoneyFactory类实现Provider接口,创建MoneySender的实例

package abstractfactory;

public class SendMoneyFactory implements Provider{
    @Override
    public Sender produce() {
        // TODO 自动生成的方法存根
        return new MoneySender();
    }
}

测试:

package abstractfactory;

/**
 * 抽象工厂模式: 好处是若你现在想增加一个功能:发及时消息,则只需做一个实现类
 * 实现Sender接口,同时做一个工厂类,实现Provider接口,就OK,扩展性好
 * @author cx
 *
 */
public class Test {

    public static void main(String[] args) {
        //创建Provider的实现类
        Provider provider=new SendMailFactory();

        //Sender sender 的实现类在Provider.produce()中
        Sender sender=provider.produce();

        sender.send();

        Provider provider2=new SendMoneyFactory();

        Sender sender2=provider2.produce();

        sender2.send();

    }
}

测试结果

this is mailsender!
this is MoneySender!

3.单例模式(Singleton)

简单说明:保证一个类只有一个实例,并提供它一个访问它的全局访问点

总的来说注意几点:

  1. 单例类只能有一个实例
  2. 单例类必须自己创建自己的唯一实例
  3. 单例类必须提供给所有其他对象提供这一实例

举例说明:

最简单的单例模式:

package singleton;

/**
 * 单例模式:在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在
 *    好处:1.某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销
 *        2.省去了new操作符,降低了系统内存的使用频率,减轻GC压力
 *        3.有些类如交易所得核心交易,控制着交易流程,只有使用单例模式,才能保证核心交易服务器独立控制整个流程
 *      
 * @author cx
 *
 *
 *1、单例模式理解起来简单,但是具体实现起来还是有一定的难度。
 *2、synchronized关键字锁定的是对象,在用的时候,一定要在恰当的地方使用
(注意需要使用锁的对象和过程,可能有的时候并不是整个对象及整个过程都需要锁)。
 */
public class Singleton {

    //该类为最基础的单例模式。但是像这样毫无线程安全保护的类,若把它放入多线程环境下,就会出问题

    //持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载
    private static Singleton instance=null;

    //私有构造方法,防止被实例化
    private Singleton(){

    }

    //静态工程方法,创建实例
    public static Singleton getInstance(){
        if(instance==null){
            instance=new Singleton();
        }
        return instance;
    }

    //如果该对象被用于序列化,可以保证对象在序列化前后保持一致
    public Object readResolve(){
        return instance;
    }




}

考虑到了线程安全问题的单例模式

package singleton;

public class Singleton2 {

    //持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载
    private static Singleton2 instance=null;

    //私有构造方法,防止被实例化
    private Singleton2(){

    }

    //解决了,像这样毫无线程安全保护的类,考虑多线程的问题
    //静态工程方法,创建实例,虽然 synchronized 关键字锁住了这个对象,但这样的用法,在性能上会有所下降
    //因为每次调用getInstance(),都要对对象上锁,事实上,只有在第一次 创建对象时需要加锁,之后就不需要了

    public static synchronized Singleton2 getInstance(){
        if(instance==null){
            instance =new Singleton2();
        }
        return instance;
    }


}
package singleton;

public class Singleton3 {

    //持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载
    private static Singleton3 instance=null;

    //私有构造方法,防止被实例化
    private Singleton3(){

    }


    //似乎解决了每次调用getInstance()创建对象时都加锁的性能下降问题
    //也就是说当调用的时候不需要加锁,只有在instance为null。并创建对象的时候才需要加锁,性能上有一定的提升
    //但是,还是有可能有问题:在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。
    //但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,
    //然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这样就可能出错了,我们以A、B两个线程为例:

    /*a>A、B线程同时进入了第一个if判断

    b>A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();

    c>由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。

    d>B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。

    e>此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。*/

    public static Singleton3 getInstance(){
        if(instance==null){
            synchronized (instance) {
                if(instance==null){
                    instance=new Singleton3();
                }
            }
        }
        return instance;
    }
}

较好的单例模式,使用静态内部类来维护单例的实现

package singleton;

/**
 * 实际情况是,单例模式使用内部类来维护单例的实现,
 * JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。
 * 这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,
 * 并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心上面的问题。
 * 同时该方法也只会在第一次调用的时候使用互斥机制,
 * 这样就解决了低性能问题。这样我们暂时总结一个完美的单例模式:
 * @author cx
 *
 */
public class SingletonGreat {

    //私有构造方法,防止被实例化
    private SingletonGreat(){

    }

    //此处写一个内部类来维护单例
    private static class SingletonFactory{
        private static SingletonGreat instance =new SingletonGreat();
    }

    //获取实例
    public static SingletonGreat getInstance(){
        //用非静态内部类会保留一个对外部类的引用,可能会造成内存泄漏,若改成静态内部类则不会有任何外部类的引用
        return SingletonFactory.instance;
    }

    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */  
    public Object readResolve() {  
        return getInstance();  
    }  
}

将创建实例和getInstance()分开的一种单例模式

package singleton;


/**
 * 也有人这样实现:因为我们只需要在创建类的时候进行同步,
 * 所以只要将创建和getInstance()分开,
 * 单独为创建加synchronized关键字,也是可以的:
 * @author cx
 *
 */
public class SingletonTest {

    private static SingletonTest instance=null;

    private SingletonTest(){

    }

    private static synchronized void syncInit(){
        if(instance ==null){
            instance =new SingletonTest();
        }
    }

    public static SingletonTest getInstance(){
        if(instance==null){
            syncInit();
        }

        return instance;
    }
}

4.建造者模式(Builder)

简单说明:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。

举例说明:

package builder;

// 建造者模式:定义
// 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示

/**
 * 抽象建造者:是为了将建造的具体过程交与它的子类来实现,这样更容易扩展
 * 一般至少会有两个抽象方法,一个用来建造产品,一个是用来返回产品
 * @author cx
 *
 */
public interface Builder {
    //建造产品
    public void setPart(String arg1,String arg2);

    //返回产品
    public Product getProduct();
}
package builder;

/**
 * 建造者类,用来实现Builder类中所有未实现的方法,具体来说一般是两项任务:
 *          组建产品;返回组建好的产品
 * @author cx
 *
 */
public class ConcreteBuilder implements Builder{

    private Product product=new Product();

    @Override
    public void setPart(String arg1, String arg2) {
        //用传的两个参数来给Product类中的属性值赋值
        product.setName(arg1);
        product.setType(arg2);
    }

    @Override
    public Product getProduct() {
        // TODO 自动生成的方法存根
        return product;
    }

}
package builder;

/**
 * 导演类:负责调用适当的建造者来组建产品,导演类一般不与产品类发生依赖关系
 * 与导演类直接交互的是建造者类
 * @author cx
 *
 */
public class Director {
    private Builder builder=new ConcreteBuilder();

    public Product getAProduct(){
        builder.setPart("宝马汽车", "X7");
        return builder.getProduct();
    }

    public Product getBProduct(){
        builder.setPart("奥迪汽车", "Q5");
        return builder.getProduct();
    }
}
package builder;

/**
 * 产品类:一般为一个较为复杂的对象,也就是说创建对象的过程比较复杂
 * 实际编程中,产品类可以是由一个抽象类与它不同实现组成,也可以是由多个抽象类与他们的实现类组成
 * @author cx
 *
 */
public class Product {

    private String name;
    private String type;

    public void showProduct(){
        System.out.println("名称:"+name);
        System.out.println("型号:"+type);
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setType(String type) {
        this.type = type;
    }


}
package builder;

/**
 * 测试建造者模式的类
 * @author cx
 *
 * 首先,建造者模式的封装性很好。在使用建造者模式的场景中,一般产品类和建造类是比较稳定的,
 * 因此将主要的业务逻辑封装在导演类中对整体而言可以取得比较好的稳定性
 * 
 * 其次,建造者模式很容易进行扩展。如有新的需求,通过实现一个新的建造者类就可以完成,不会对原有功能引入风险
 * 与工厂模式相比,建造者模式一般是用来创建更为复杂的对象
 */
public class Test {
    public static void main(String[] args) {
        Director director=new Director();

        //通过Director 来创建 Product的实例
        Product product1=director.getAProduct();
        product1.showProduct();

        Product product2=director.getBProduct();
        product2.showProduct();
    }
}

测试结果

这里写代码片名称:宝马汽车
型号:X7
名称:奥迪汽车
型号:Q5

其实还有许多Builder(建造者模式)的变种,比如结合javabean对象一起使用,通过静态内部类。

package user;

/*
 * UserThree类的构造函数是私有的,这意味着调用者不能直接实例化这个类
 * UserThree类是不可变的,所有必选的属性值都是final的并且在构造函数中设置,同时属性值只提供getter函数
 * 
 */
public class UserThree {
    private final String mOne;
    private final String mTwo;
    private final int mAge;
    private final String mPhoneNo;

    private UserThree(UserBuilder userBuilder){
        //私有构造setter 方法
        mOne=userBuilder.mOne;
        mTwo=userBuilder.mTwo;
        mAge=userBuilder.mAge;
        mPhoneNo=userBuilder.mPhoneNo;
    }


    @Override
    public String toString() {
        return "UserThree [mOne=" + mOne + ", mTwo=" + mTwo + ", mAge=" + mAge + ", mPhoneNo=" + mPhoneNo + "]";
    }


    // 公共构造getter方法
    public String getmOne() {
        return mOne;
    }



    public String getmTwo() {
        return mTwo;
    }



    public int getmAge() {
        return mAge;
    }



    public String getmPhoneNo() {
        return mPhoneNo;
    }



    public static class UserBuilder{
        // 静态内部类

        private String mOne;   //必选
        private String mTwo;   //必选
        private int mAge;      //可选
        private String mPhoneNo;//可选

        public UserBuilder(String mOne,String mTwo){
            this.mOne=mOne;
            this.mTwo=mTwo;
        }

        public UserBuilder mAge(int mAge){
            this.mAge=mAge;
            return this;
        }

        public UserBuilder mPhoneNo(String mPhoneNo){
            this.mPhoneNo=mPhoneNo;
            return this;
        }

        public UserThree build(){
            return new UserThree(this);  //构造私有类中的内部静态类
        }
    }

    public UserThree getUser(){
        //返回实例
        return new UserThree.UserBuilder("cx", "123")
                            .mAge(20)
                            .mPhoneNo("151********")
                            .build();
    }

    public static void main(String[] args) {
        UserThree userThree=new UserThree.UserBuilder("cx", "123")
                .mAge(20)
                .mPhoneNo("151********")
                .build();

        System.out.println(userThree.toString());
    }


}

测试结果

UserThree [mOne=cx, mTwo=123, mAge=20, mPhoneNo=151********]

5、原型模式(Prototype)

简单说明:是用于创建重复的对象,同时又能保证性能,用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

举例说明:

package prototype;

import java.util.ArrayList;

/**
 * 原型模式:定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象
 * 创建类模式
 * @author cx
 *
 * 原型模式主要用于对象的赋值,Prototype 类需要满足以下2个条件
 * 1.实现Cloneable 接口。它的作用只有一个,
 *    就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。
 *    在java虚拟机中,只有实现了这个接口的类才可以被拷贝,
 *    否则在运行时会抛出CloneNotSupportedException异常。
 * 
 * 2.重写Object类中的clone方法。Object类中有一个clone方法,
 *     作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,
 *     因此,Prototype类需要将clone方法的作用域修改为public类型。
 */


 //由于Cloneable 是个空接口,所以你可以任意定义实现类的方法名
 //super.clone() 调用的是Object的clone() 方法


 /**
  * 对象的浅复制:将一个对象复制后,基本数据类型的变量会重新创建,而引用对象,指向的还是原对象所指向的
  * 
  * 对象的深复制:将一个对象复制后,不论是基本数据类型还是引用类型,都是重新创建的,完全复制
  * @author cx
  *
  */
public class Prototype implements Cloneable{

    private ArrayList list=new ArrayList<>();
    @Override
    public Object clone() throws CloneNotSupportedException {
        // TODO 自动生成的方法存根
        Prototype prototype=(Prototype) super.clone();

        prototype.list = (ArrayList) this.list.clone();  
        //  由于ArrayList不是基本类型,所以成员变量list,深拷贝时,不会被拷贝,
        //需要我们自己实现深拷贝,幸运的是Java提供的大部分的容器类都实现了Cloneable接口。
        //所以实现深拷贝并不是特别困难。

        return prototype;
    }
}
package prototype;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Prototype2 implements Cloneable,Serializable{

    private static final long serialVersionUID=1L; //可以更好的反序列化
    private String string;

    private SerializableObject object;

    /**
      * 对象的浅复制:将一个对象复制后,基本数据类型的变量会重新创建,而引用对象,指向的还是原对象所指向的
      * 
      * 对象的深复制:将一个对象复制后,不论是基本数据类型还是引用类型,都是重新创建的,完全复制
      * @author cx
      *
      */

    //浅复制
    @Override
    public Object clone() throws CloneNotSupportedException {
        Prototype prototype=(Prototype) super.clone();
        return prototype;
    }

    //深复制

    //要实现深复制,需要采用流的形式读入当前对象的二进制输入,再写出二进制数据对应的对象。
    public Object deepClone() throws IOException,ClassNotFoundException{
        //写入当前对象的二进制流
        ByteArrayOutputStream bos=new ByteArrayOutputStream();

        ObjectOutputStream oos=new ObjectOutputStream(bos);

        oos.writeObject(this);

        //读出二进制流产生的新对象
        ByteArrayInputStream bis=new ByteArrayInputStream(bos.toByteArray());

        ObjectInputStream ois=new ObjectInputStream(bis);

        return ois.readObject();

    }

    public String getString() {
        return string;
    }

    public void setString(String string) {
        this.string = string;
    }

    public SerializableObject getObject() {
        return object;
    }

    public void setObject(SerializableObject object) {
        this.object = object;
    }



}

class SerializableObject implements Serializable{
    private static final long serialVersionUID=1L; //可以更好的反序列化
}
package prototype;

public class ConcretePrototype extends Prototype{

    public void show(){
        System.out.println("原型模式实现类");
    }
}
package prototype;

import java.util.ArrayList;

/**
 * 原型模式的优点:
 * 
 * 使用原型模式创建对象比直接new一个对象在性能上要好的多,
 * 因为Object类的clone方法是一个本地方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。
 * 使用原型模式的另一个好处是简化对象的创建,使得创建对象就像我们在编辑文档时的复制粘贴一样简单。
 * 因为以上优点,所以在需要重复地创建相似对象时可以考虑使用原型模式。
 * 比如需要在一个循环体内创建对象,假如对象创建过程比较复杂或者循环次数很多的话,
 * 使用原型模式不但可以简化创建过程,而且可以使系统的整体性能提高很多。
 * 
 * @author cx
 *
 *
 *原型模式的注意事项:
    使用原型模式复制对象不会调用类的构造方法。
    因为对象的复制是通过调用Object类的clone方法来完成的,
    它直接在内存中复制数据,因此不会调用到类的构造方法。
    不但构造方法中的代码不会执行,甚至连访问权限都对原型模式无效。
    还记得单例模式吗?单例模式中,只要将构造方法的访问权限设置为private型,
    就可以实现单例。但是clone方法直接无视构造方法的权限,
    所以,单例模式与原型模式是冲突的,在使用时要特别注意。
 */
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        ConcretePrototype cp=new ConcretePrototype();

        for(int i=0;i<10;i++){
            ConcretePrototype clonecp=(ConcretePrototype) cp.clone(); //复制10个对象
            clonecp.show();
        }
    }
}

测试结果

原型模式实现类
原型模式实现类
原型模式实现类
原型模式实现类
原型模式实现类
原型模式实现类
原型模式实现类
原型模式实现类
原型模式实现类
原型模式实现类

猜你喜欢

转载自blog.csdn.net/m0_37094131/article/details/70738934