Leetcode-设计模式-构建型模式

Posted 钢铁-程序猿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Leetcode-设计模式-构建型模式相关的知识,希望对你有一定的参考价值。

文章目录

设计模式-构建型模式

一、工厂模式

1、简单工厂模式(让一个工厂创建所有对象)

工厂模式缺点:

  • 让一个工厂类承担构建所有对象的职责,一是如果需要生产的产品过多,此模式会导致工厂类过于庞大,承担过多的职责,变成超级类。违背了单一职责原则
  • 二是当要生产新的产品时,必须在工厂类中添加新的分支。而开闭原则告诉我们:类应该对修改封闭。我们希望在添加新功能时,只需增加新的类,而不是修改既有的类,所以这就违背了开闭原则
public class FruitFactory 
    public Fruit create(String type) 
        switch (type) 
            case "苹果":
                AppleSeed appleSeed = new AppleSeed();
                Sunlight sunlight = new Sunlight();
                Water water = new Water();
                return new Apple(appleSeed, sunlight, water);
            case "梨子":
                return new Pear();
            default:
                throw new IllegalArgumentException("暂时没有这种水果");
        
    

2、工厂方法模式(每个产品有一个专属工厂)

虽然每个产品都有自己的工厂,但是还是满足单一职责和开闭原则的。

  • 1、如果苹果的生产过程需要修改时,只需修改苹果工厂,调用方不需要修改。梨子的生产过程需要修改时,只需修改梨子工厂。符合单一职责原则。
  • 2、当需要生产新的产品时,无需更改既有的工厂,只需要添加新的工厂即可。保持了面向对象的可扩展性,符合开闭原则。
public class User 
    private void eat()
        AppleFactory appleFactory = new AppleFactory();
        Fruit apple = appleFactory.create();
        PearFactory pearFactory = new PearFactory();
        Fruit pear = pearFactory.create();
        apple.eat();
        pear.eat();
    

3、抽象工厂模式(定义接口,提取出工厂的接口,使用时无需关心是哪个工厂类,适合横向扩展(新增工厂),不适合纵向扩展(新增功能))

提供接口,我们在创建时指定了具体的工厂类后,在使用时就无需再关心是哪个工厂类,只需要将此工厂当作抽象的 IFactory 接口使用即可。这种经过抽象的工厂方法模式被称作抽象工厂模式。

public class User 
    private void eat()
        IFactory appleFactory = new AppleFactory();
        Fruit apple = appleFactory.create();
        IFactory pearFactory = new PearFactory();
        Fruit pear = pearFactory.create();
        apple.eat();
        pear.eat();
    

实际上抽象工厂模式主要用于替换一系列方法。

例如将程序中的 SQL Server 数据库整个替换为 Access 数据库,使用抽象方法模式的话,只需在 IFactory 接口中定义好增删改查四个方法,让 SQLFactory 和 AccessFactory 实现此接口,调用时直接使用 IFactory 中的抽象方法即可,调用者无需知道使用的什么数据库,我们就可以非常方便的整个替换程序的数据库,并且让客户端毫不知情。

抽象工厂模式很好的发挥了开闭原则、依赖倒置原则,但缺点是抽象工厂模式太重了,如果 IFactory 接口需要新增功能,则会影响到所有的具体工厂类。所以抽象工厂模式适用于增加同类工厂这样的横向扩展需求,不适合新增功能这样的纵向扩展。

二、单例模式

1、饿汉式

饿汉式:变量在声明时便初始化。

public class Singleton 
  
    private static Singleton instance = new Singleton();

    private Singleton() 
    

    public static Singleton getInstance() 
        return instance;
    

2、懒汉式

public class Singleton 
    
    private static Singleton instance = null;

    private Singleton() 
    

    public static Singleton getInstance() 
        if (instance == null) 
            synchronized (Singleton.class) 
                if (instance == null) 
                    instance = new Singleton();
                
            
        
        return instance;
    

问:双检锁单例模式中,volatile 主要用来防止哪几条指令重排序?如果发生了重排序,会导致什么样的错误?

答案:

instance = new Singleton();

这一行代码中,执行了三条重要的指令:

  • 分配对象的内存空间
  • 初始化对象
  • 将变量 instance 指向刚分配的内存空间

在这个过程中,如果第二条指令和第三条指令发生了重排序,可能导致 instance 还未初始化时,其他线程提前通过双检锁外层的 null 检查,获取到“不为 null,但还没有执行初始化”的 instance 对象,发生空指针异常。

三、建造者模式(通过链式调用创建对象,建造者模式主要应用于构建过程稳定,可通过不同配置建造出不同对象的场景。)

意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
何时使用:一些基本部件不会变,而其组合经常变化的时候。

使用建造者模式表示如下:

public class MilkTea 
    private final String type;
    private final String size;
    private final boolean pearl;
    private final boolean ice;

	private MilkTea() 
	
    private MilkTea(Builder builder) 
        this.type = builder.type;
        this.size = builder.size;
        this.pearl = builder.pearl;
        this.ice = builder.ice;
    

    public String getType() 
        return type;
    

    public String getSize() 
        return size;
    

    public boolean isPearl() 
        return pearl;
    
    public boolean isIce() 
        return ice;
    

    public static class Builder 

        private final String type;
        private String size = "中杯";
        private boolean pearl = true;
        private boolean ice = false;

        public Builder(String type) 
            this.type = type;
        

        public Builder size(String size) 
            this.size = size;
            return this;
        

        public Builder pearl(boolean pearl) 
            this.pearl = pearl;
            return this;
        

        public Builder ice(boolean cold) 
            this.ice = cold;
            return this;
        

        public MilkTea build() 
            return new MilkTea(this);
        
    

public class User 
    private void buyMilkTea() 
        MilkTea milkTea = new MilkTea.Builder("原味").build();
        show(milkTea);

        MilkTea chocolate =new MilkTea.Builder("巧克力味")
                .ice(false)
                .build();
        show(chocolate);
        
        MilkTea strawberry = new MilkTea.Builder("草莓味")
                .size("大杯")
                .pearl(false)
                .ice(true)
                .build();
        show(strawberry);
    

    private void show(MilkTea milkTea) 
        String pearl;
        if (milkTea.isPearl())
            pearl = "加珍珠";
        else
            pearl = "不加珍珠";
        String ice;
        if (milkTea.isIce()) 
            ice = "加冰";
         else 
            ice = "不加冰";
        
        System.out.println("一份" + milkTea.getSize() + "、"
                + pearl + "、"
                + ice + "的"
                + milkTea.getType() + "奶茶");
    

可以看到,我们将 MilkTea 的构造方法设置为私有的,所以外部不能通过 new 构建出 MilkTea 实例,只能通过 Builder 构建。对于必须配置的属性,通过 Builder 的构造方法传入,可选的属性通过 Builder 的链式调用方法传入,如果不配置,将使用默认配置,也就是中杯、加珍珠、不加冰。

四、原型模式

手写clone方法

public class MilkTea
    public String type;
    public boolean ice;

    public MilkTea clone()
        MilkTea milkTea = new MilkTea();
        milkTea.type = this.type;
        milkTea.ice = this.ice;
        return milkTea;
    

private void order()
    MilkTea milkTeaOfJay = new MilkTea();
    milkTeaOfJay.type = "原味";
    milkTeaOfJay.ice = false;
    
    MilkTea yourMilkTea = milkTeaOfJay.clone();
    
    // 一千位粉丝都调用 milkTeaOfJay 的 clone 方法即可
    ...

java自带的Cloneable(不需手写Clone方法)

public class MilkTea implements Cloneable
    public String type;
    public boolean ice;

    @NonNull
    @Override
    protected MilkTea clone() throws CloneNotSupportedException 
        return (MilkTea) super.clone();
    

值得注意的是,Java 自带的 clone 方法是浅拷贝的。也就是说调用此对象的 clone 方法,只有基本类型的参数会被拷贝一份,非基本类型的对象不会被拷贝一份,而是继续使用传递引用的方式。如果需要实现深拷贝,必须要自己手动修改 clone 方法才行。

总结

  • 工厂方法模式:为每一类对象建立工厂,将对象交由工厂创建,客户端只和工厂打交道。
  • 抽象工厂模式:为每一类工厂提取出抽象接口,使得新增工厂、替换工厂变得非常容易
  • 建造者模式:用于创建构造过程稳定的对象,不同的 Builder 可以定义不同的配置。
  • 单例模式:全局使用同一个对象,分为饿汉式和懒汉式。懒汉式有双检锁和内部类两种实现方式。
  • 原型模式:为一个类定义 clone 方法,使得创建相同的对象更方便。

以上是关于Leetcode-设计模式-构建型模式的主要内容,如果未能解决你的问题,请参考以下文章

Leetcode-设计模式-行为型模式

Leetcode-设计模式-行为型模式

设计模式:简单工厂工厂方法抽象工厂区别

Leetcode-设计模式-行为型模式

如何使用 phonegap 构建同时支持纵向和横向启动屏幕?

如何确定用户的 iPhone 是不是处于纵向模式?