23种设计模式之创建者模式(单例模式,工厂模式,原型模式,建造者模式)

一、创建者模式

1.单例模式

1.1什么是单例模式

        单例模式是java中最简单的设计模式之一。这种类型的设计模式属于创建者模式,他提供了一种创建对象的最佳方式。

        这种模式涉及一个单一的类,该类负责创建自己的对象,同时保证只有单个对象被创建。这个类提供唯一的的对象的方式,可以直接访问,不需要实例化该类的对象。

1.2单例模式的结构

        单例模式只有两个角色

1.单例类:只能创建一个实例的类

2.访问类:使用单例类

1.3单例模式的实现

        单例模式有两种:

               饿汉式:顾名思义,在类加载时就会创建该对象

               懒汉式 :就是在使用时才回去创建该对象

 1.饿汉式(1)——静态变量

public class Singletion {
    //私有构造方法
   private Singletion(){};
   private static  Singletion instance=new Singletion();
   public static Singletion getInstance(){
       return instance;
   }
}

验证时可创建两个对象看是否地址值相同

2.饿汉式(2)——静态代码块

        两种实现方式差不多,静态变量是在声明变量时就给予赋值,而静态代码块是在静态代码块中赋值

public class Singletion {
    //私有构造方法
   private Singletion(){};
   private static  Singletion instance;
   static {
       instance =new Singletion();
   }
   public static Singletion getInstance(){
       return instance;
   }
}

饿汉式的创建是随着类的加载而加载,浪费资源的现象难以避免。

3.懒汉式(1)

public class Singletion {
    //私有构造方法
   private Singletion(){};
   private static  Singletion instance;
   
   public static Singletion getInstance(){
       //当对象为空的时候才会去创建对象,即首次使用才会创建
       if (instance==null){
           Singletion singletion = new Singletion();
       }
       return instance;
   }
}

这种模式下在多线程情况下就会出现并发问题,假如在判空时候线程一刚进来,突然cpu控制权交给了线程二。此时线程一并没有去创建对象,线程二仍会进来,此时线程一跟线程二都会创建一个新的对象此时就会有两个对象。那么有没有解决方案呢?肯定有的,看下面一种

4.懒汉式(2)——单检锁

public class Singletion {
    //私有构造方法
   private Singletion(){};
   private static  Singletion instance;

   public static synchronized Singletion getInstance(){
       //当对象为空的时候才会去创建对象,即首次使用才会创建
       if (instance==null){
           Singletion singletion = new Singletion();
       }
       return instance;
   }
}

加上锁以后就可以解决单例问题,但这种实现方式有没有弊端呢?弊端肯定是有的,因为呢来获取对象,几乎都是读操作,每次读操作都需要加锁,效率肯定不高,那么有没有好的解决办法呢?

这个也是有的

5.懒汉式(3)——双检锁

public class Singletion {
    //私有构造方法
   private Singletion(){};
   private static  Singletion instance;

   public static  Singletion getInstance(){
       //判断对象是否为空
       if (instance==null){
           //此时可能并发进来多个线程。加锁
           synchronized(Singletion.class){
               //在进行判断是否为空,不为空,直接返回
               if (instance==null){
                   Singletion singletion = new Singletion();
               }
           }

       }
       return instance;
   }
}

这种实现方案解决了单例,线程安全跟效率的问题。那么有没有问题呢?也是有的,这种设计模式可能会由于指令重排序的问题出现空指针。只需要在对象上加上volation关键字即可。想详细了解为什么的可以参考我以下这篇文章

双检锁创建单例为什么会出现空指针-CSDN博客

6.懒汉模式(4)——静态内部类

public class Singletion {
    //私有构造方法
   private Singletion(){};
   private static  Singletion instance;
   private static class SingletonHolder{
       private static final Singletion INSTANCE =new Singletion();

   }
   public static  Singletion getInstance(){

       return SingletonHolder.INSTANCE;
   }
}

这种实现模式是很优秀的单例模式,也是开源项目中常用的单例模式。在不加任何锁的情况下,保证了多线程的安全,并且没有任何性能影响和空间的浪费。

7.枚举——恶汉模式

public enum Singletion {
    INSTANCE;
}

1.4存在问题

单例模式是可能被破坏的,当然恶汉模式除外;

        序列化跟反序列化

        反射

利用这两种方法创建的对象都不是单例的。

1.5解决方案

解决序列化问题就是在类中添加readResolve()方法,在反序列化时会被调用这个方法,如果定义了这个方法就会返回这个方法的值,如果未定义,那么就返回new出来的对象。

反射,因为反射破坏单例模式的方法就是去越过检查去使用构造方法就创建,只需要在构造方法上判断对象是否为null如果不是则创建,如果是则抛出异常。

2.工厂方法模式

2.1什么是工厂方法模式

        工厂模式模式是符合开闭原则的,以咖啡店做咖啡为例,咖啡店员工在做咖啡时,不需要将所有品种的咖啡品类都做一出来供客户选择,而是通过客户选择什么,针对客户的选择去制作咖啡,我们代码也需要实现这一需求。

 2.2工厂方法模式的实现

          这是一个咖啡类,具有加奶加糖功能

public abstract class Coffer {
    private String name;
    public abstract void addMilk();


    public abstract void addSugar();
}

     然后分别是两种咖啡的实现类

public class AmericanoCoffer extends Coffer {
    private String name ="美式咖啡";
    @Override
    public void addMilk() {
        System.out.println("美式咖啡加奶");
    }

    @Override
    public void addSugar() {
        System.out.println("美式咖啡加糖");
    }
}
public class LatteCoffee extends Coffer {
    private String name ="拿铁咖啡";
    @Override
    public void addMilk() {
        System.out.println("拿铁咖啡加奶");
    }

    @Override
    public void addSugar() {
        System.out.println("拿铁咖啡加糖");
    }
}

            这是咖啡工厂类,可以理解为用来创建一种咖啡的模具

public interface CofferFactory {
    public Coffer creatCoffer();
}

             然后是两种咖啡工厂实现类

public class AmericanoCofferFactory implements CofferFactory {
    @Override
    public Coffer creatCoffer() {
        return new AmericanoCoffer();
    }
}
public class LatteCofferFactory implements CofferFactory {
    @Override
    public Coffer creatCoffer() {
        return new LatteCoffee();
    }
}

        咖啡店类

public class CofferStory {
    private CofferFactory cofferFactory;
    public  CofferStory(CofferFactory cofferFactory){
    this.cofferFactory=cofferFactory;
    }
    public Coffer createCoffer(){
        return cofferFactory.creatCoffer();
    }
}

        模拟客户点单

public class Demo {
    public static void main(String[] args) {
        CofferFactory cofferFactory = new AmericanoCofferFactory();
        CofferStory cofferStory = new CofferStory(cofferFactory);
        Coffer coffer = cofferStory.createCoffer();
        coffer.addMilk();
        coffer.addSugar();
        System.out.println(coffer.getName());
    }
}

这种设计模式满足开闭原则,即对修改开放对修改关闭,每次咖啡店增加新的咖啡品类,只需增加新的咖啡制作容器跟咖啡品类即可。降低了耦合性。

2.3工厂方法模式的优缺点

        优点:

        1.客户只需要知道咖啡的品类即可,无需知道咖啡生产的过程,将创建咖啡的过程封装在工厂内。

        2.每次咖啡店增加新的咖啡品类,只需增加新的咖啡制作容器跟咖啡品类即可。降低了耦合性。符合开闭原则

        缺点:

        每增加一个产品,就需要增加一个具体产品类,跟构建产品类,假如产品非常多就会造成类爆炸,增加了系统的复杂性。

3.抽象工厂模式

        3.1什么是抽象工厂模式

        抽象方法模式跟方法模式差不多唯一区别是多了一个产品族的概念,下面为大家举例,大米公司即制造手机也制造平板跟电脑,香蕉公司也生产手机平板跟电脑。那么,大米公司下的所有产品就被称为一个产品族,同理香蕉也一样,产品集就是大米手机跟香蕉手机算一个产品集。,平板跟电脑同理,说白了就是在工厂方法的基础上添加制造手机跟制造电脑的方法。由于过于简单,不在做赘述了。

        3.2抽象工厂模式缺点

        抽象工厂的缺点很明显,当一个公司增加了一个商品,其余公司也要跟着增加,所以适用场景有限。

3.3.工厂模式练习(简单工厂跟配置文件)

      

public class CofferFactory {
    //用来存储所有的咖啡产品
    private static HashMap<String,Coffer> cofferMap=new HashMap<>();
    //加载配置文件,创建咖啡
    static {
        Properties properties = new Properties();
        InputStream is = CofferFactory.class.getClassLoader().getResourceAsStream("D:\\projects\\shejimoshi\\src\\confihg_Factory\\bean.properties");

        try {
            properties.load(is);
            Set<Object> keySet = properties.keySet();
            for (Object o : keySet) {
                String className = (String) properties.get(String.valueOf(o));
                Class aClass = Class.forName(className);
                Coffer coffer = (Coffer) aClass.newInstance();
                cofferMap.put(String.valueOf(o),coffer);

            }

        } catch (Exception e) {
           e.printStackTrace();
        }
    }

    /**
     * 用来获取咖啡
     * @param cofferName
     * @return
     */
    private Coffer creatCoffer(String cofferName){
        return cofferMap.get(cofferName);
    }

}

        这个联系很简单,就是读取配置文件种的全类名,然后去利用反射技术创建类对象即可,存在静态map中,随取随用,有学过spring的同事肯定会觉得这个跟ioc差不多嘛这不?哎别说,他底层还真是这样的。等所有的模式介绍完,我们将会简单实现一下Ioc。

        这种模式呢同样符合开闭原则,每次只需要加载一次,在增加时只需要添加新增的咖啡类,跟配置文件的配置即可

3.4jdk源码那些方法用的工厂模式

       迭代器大家应该不陌生吧,让我们一起去看一下他是怎么使用的工厂模式。

在collect接口中有一个方法返回值就是

那我们再来看一下Arraylist的实现

在返回值当中返回了一个Itr对象

点进来发现Itr是实现了Iterator

当看到这你会发现,Collection相当于抽象工厂,而arrayList就是具体工厂,Iterator是抽象产品,而Itr则是具体产品。

在jdk源码中用到的工厂模式很多,Dateforamt类中的getInstance()方法,Calendar类中的getInstance()方法都是用的工厂模式,在这不带大家一起去看了。

4.原型模式

4.1什么是克隆模式

        克隆模式就是使用一个已经创建好的实例作为原型,通过复制该对象来创建一个很原型相同的对象。

4.2克隆模式的结构

        1.抽象原型类:规定了具体原型类必须实现的clone()方法。

        2.具体原型类:实现抽象原型类的clone()方法,他是可复制对象

        3.访问类:使用具体原型中的clone()方法复制新的对象

4.3克隆模式的实现

        具体原型类,Cloneable是抽象原型类

public class Realizetype implements Cloneable{
    public Realizetype(){
        System.out.println("具体对象创建成功");
    }
    @Override
    protected Realizetype clone() throws CloneNotSupportedException {
        System.out.println("克隆对象克隆成功");
        return (Realizetype) super.clone();
    }
}

访问类

public class Demo {
    public static void main(String[] args) throws CloneNotSupportedException {
        Realizetype realizetype = new Realizetype();
        Realizetype clone = realizetype.clone();
        System.out.println("两个对象是否为同一个对象"+(realizetype==clone));
    }
}

当我们运行代码时会发现控制台输出

这说明,克隆出来的对象不是一个对象,并且clone时并不是通过new出来的

4.4原型模式案例

同一学校的奖状只有名字不同其他都相同,可以用原型模式创建多个奖状,然后修改名字即可

public class Citation implements Cloneable{
    private String name;

    public String getName() {
        return name;
    }

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

    @Override
    protected Citation clone() throws CloneNotSupportedException {
        return (Citation) super.clone();
    }
    public void  show(){
        System.out.println(name+"同学在本学期成绩优秀,评为三好学生,特发此状,以资鼓励");
    }
}

然后访问类进行访问

public class Demo {
    public static void main(String[] args) throws CloneNotSupportedException {
        Citation citation = new Citation();
        Citation clone = citation.clone();
        citation.setName("张三");
        clone.setName("李四");
        citation.show();
        clone.show();
    }
}

4.5使用场景

对象创建非常复杂

对性能和安全要求便较高

4.6扩展(深克隆)

        浅克隆是创建对象时,若对象存在引用对象类型,那么还会指向原来的对象地址,而深克隆则会克隆引用对象类型,即新的对象地址。

在上述练习中,用到的就是浅克隆,当name属性是从student类中获取时修改其中的一个姓名那么其他的都会跟着改变。但深克隆就不会,这里建议用序列化的方式实现深克隆,因与上练习差不多。不再引入代码展示,只提供思路,感兴趣的朋友可以去试一下

5.建造者模式

5.1什么是建造者模式

        建造者模式就是将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。即实现定义跟构建的解耦,举个例子,我在制造一个手机时,我只需要声明其各个部件即可并不需要了解其详细的构建过程·,这就是建造者模式。

5.2建造者模式的结构

建造者模式主要包含以下角色:

  • 抽象建造者类:这个接口规定要实现复杂对象的创建,并不涉及具体对象的创建部件的过程。
  • 具体建造者类:实现抽象建造者类接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
  • 产品类:要实现的复杂对象
  • 指挥者类:调用建造者类来完成复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证各部分完整创建或者按照某种顺序创建

5.3建造者模式的实现

以自行车为例,先创建自行车类,并有车座,车架俩参数

public class bike {
    private String saddle;
    private String frame;

    @Override
    public String toString() {
        return "bike{" +
                "saddle='" + saddle + '\'' +
                ", frame='" + frame + '\'' +
                '}';
    }

    public String getSaddle() {
        return saddle;
    }

    public void setSaddle(String saddle) {
        this.saddle = saddle;
    }

    public String getFrame() {
        return frame;
    }

    public void setFrame(String frame) {
        this.frame = frame;
    }
}

然后是抽象建造者类

public abstract class bikeBuilder {
    protected bike bike = new bike();

    public abstract void setSaddle();
    public abstract void setFrame();
    public abstract bike bikeBuilder();
}

然后是具体建造者类

public class haluoBikeBikeBuilder extends bikeBuilder {
    @Override
    public void setSaddle() {
        bike.setSaddle("真皮座椅");
    }

    @Override
    public void setFrame() {
        bike.setFrame("碳纤维车架");
    }

    @Override
    public bike bikeBuilder() {
        return bike;
    }
}

最后控制者

public class Commander {
    private bikeBuilder bikeBuilder;
    public Commander(bikeBuilder bikeBuilder){
        this.bikeBuilder=bikeBuilder;
    }
    public bike construct(){
        bikeBuilder.setFrame();
        bikeBuilder.setSaddle();
        return bikeBuilder.bikeBuilder();
    }
}

5.4建造者模式的优缺点

优点:

  • 建造者模式的封装性很好使用建造者模式可以有效地封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体来演,有很好的稳定性。
  • 在建造者模式中,用户不需要指导产品具体内部组成的细节,将产品本身与创建过程解耦,使得,相同的创建过程可以创建不同的产品对象。
  • 可以更加详细的控制产品的创建过程。将复杂的产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
  • 建造者模式很容易进行扩展,如果有新的许需求,通过实现一个新的建造者模式就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险,符合开闭原则。

缺点:

  • 建造者模式创建的产品一般有较多的共同点,其组成部分相似,如果产品之间的差异较大就不适合使用建造者模式,因此其适用范围受到一定的限制。

5.5使用场景

建造者模式通常创建的是复杂对象,其产品的各个部分面临剧烈的变化但将他们组合一起的算法却相稳定,所以他通常在以下场合使用。

  • 创建的对象较为复杂,由多个部件构成,各部件面临着复杂的变化,构件间的构造顺序是稳定的。
  • 创建复杂对象的算法独立于该对象得待组成部分以及他们的装配方式,及产品的构建过程和最终的产品是独立的。

5.6模式的扩展

建造者模式除了上面所述以外,在开发中还有一个常用的使用方式,就是当一个类需要很多参数时,创建这个类实例时代码可读性会非常差,而且很容易引入错误,此时就可以利用建造者模式进行重构。

public class Phone {
    private String cpu;
    private String screen;
    private String battery;
    private String camera;

    @Override
    public String toString() {
        return "Phone{" +
                "cpu='" + cpu + '\'' +
                ", screen='" + screen + '\'' +
                ", battery='" + battery + '\'' +
                ", camera='" + camera + '\'' +
                '}';
    }

    private Phone(builder builder){
        this.cpu=builder.cpu;
        this.screen=builder.screen;
        this.battery=builder.battery;
        this.camera=builder.camera;
    }
    public static final class builder{
        private String cpu;
        private String screen;
        private String battery;
        private String camera;
        public builder cpu(String cpu){
            this.cpu=cpu;
            return this;
        }
        public builder screen(String screen){
            this.screen=screen;
            return this;
        }
        public builder battery(String battery){
            this.battery=battery;
            return this;
        }
        public builder camera(String camera){
            this.camera=camera;
            return this;
        }
        public Phone builder(){
            return new Phone(this);
        }
    }
}

测试类

public class demo {
    public static void main(String[] args) {
        Phone phone = new Phone.builder()
                .cpu("888")
                .battery("5000")
                .camera("徕卡")
                .screen("华星").builder();
        System.out.println(phone);
    }
}

创建者模式就到此结束了,后续还会为大家结构型模式跟行为型模式,点个关注加收藏吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值