工厂模式和建造者模式

279 阅读8分钟

前言

这两天复习OKHttp+Retrofit源码时,看到了一些设计模式,平时写业务逻辑或多或少会使用的,简单记录一下

区别:工厂模式和建造者模式同属于创建类模式,但关注的维度不一样,工厂模式关于重点是创建出一个产品,即对象,而建造者模式关注重点是如何创建,如何Builder、按何种顺序Builder出该产品对象,即产品组装细节。

工厂模式(定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类)优缺点

  • 优点 : 封装性、扩展性、对象创建和使用分离,降低了代码耦合、减少了重复代码、统一管理创建对象的不同实现逻辑、围绕着特定的抽象产品(一般是接口)来封装对象的创建过程 
  • 缺点:抽象接口新增方法会增加开发成本、具体实现工厂不统一会增加代码的理解难度

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

  • 优点:封装性、建造者容易独立和扩展、便于控制细节风险(建造过程细化,不会对其他模块产生影响)
  • 缺点:使用范围有限(创建的不同对象间如果差异性很大就不适用)、容易引起超大的类、代码行数增加

目录

一、工厂模式

平时用工厂模式是解决什么类型的问题?出于什么初衷引入了工厂模式?引入工厂模式后又引入了什么问题?

PS : 不要过度用设计模式,不要因为设计模式而设计模式

1、解决的问题

在数据库相关开发中,如果使用JDBC 连接数据库,假如这样写

ADriver aDriver = new ADriver();
aDriver......//数据库操作

那下次有个需求,需要把数据库从MySQL切换到Oracle,那是不是又得改一下代码

BDriver dDriver = new BDriver();
bDriver.....

这样只要新增一个不同的数据库连接,就需要显示new 一个出来,而且管理维护起来很不方便,如果是协同开发的话,B负责实现的Driver创建比较复杂,而你要使用Driver类,你说你的心会不会加速跳动起来,所以这种问题是不是很值得优化?

2、如何解决

我们期望使用B同事实现的Driver相关类时,只需要改动的地方就是切换一下驱动名称,其它都不需要修改,这样最好了,解决方法例如下面的简单工厂类

public class DriverFactory {
    public static <T extends Driver> T createDriver(Class<T> c){
        Driver driver = null;
        try{
            driver = (Driver) Class.forName(c.getName()).newInstance();
        }catch (Exception e){
​
        }
        return (T)driver;
    }
}
​

调用的时候只需要传入Driver的子类,是不是简单了,假如新增一个Driver类型,例如MongoDBDriver extend Driver,你可以很简单的通过工厂创建和使用它。

对此你应该对简单工厂有了一个简单的了解,接下来看另一个简单工厂类

public class OperationFactory {
    private OperationFactory(){}
​
    public static IOperation createrOperation(String name){
        if(name == null){
            return null;
        }
        if(name.equals("+")){
            return new OperationAdd();
        }else if(name.equals("-")){
            return new OperationSub();
        }else{
            return null;
        }
    }
}

如果我们要加新的运算方法类,直接在工厂类中增加一个 if else 判断即可,如果要修改已有类的某个运算规则,直接修改该类即可,也就是会修改OperationFactory工厂类和已有运算类的代码,即扩展和修改都开放了,违背了开闭原则,但它仍然是一个非常实用的设计模式。还有一个问题就是每次修改工厂类的 if else ,最后将会造成工厂类庞大无比,即维护问题。

既然简单工厂模式有问题,那就必须对该问题进行解决,进而引入了工厂模式的升级版,多个工厂类,写法如下

public class OperationAddFactory implements OperationFactory {
​
    @Override
    public IOperation getOperation() {
        return new OperationAdd();
    }
}
public class OperationSubFactory implements OperationFactory {
​
    @Override
    public IOperation getOperation() {
        return new OperationSub();
    }
}
public interface OperationFactory {
    IOperation getOperation();
}
​

每次使用如下

OperationFactory factory = new OperationAddFarcory();
IOperation operation = factory.getOperation();

这是不是解决了简单工厂模式中对修改开放的问题了(新增一个算法需求,就增加一个运算类和运算工厂) ,但是这又增加了复杂性,B同事写的每个工厂类我都得仔细了解么?有没有更好的方法让其他人使用?一般做法会新增一个协调类,用来管理工厂类,这种一般会在复杂项目里见到。

BUT,用这种多工厂方法模式,每次都新建一个工厂和对应的类,岂不是很累。确实很累,反正适合项目的就是最好的。

为了解决上面说到的这个缺点,再引入一下抽象工厂模式,看看是否能解决问题

既然是抽象工厂模式,那么理解上可以再抽象点,可以把上面提到的运算类看作不同的产品,每个工厂类对应不同的产品线,假如对运算类A有不同的运算需求,那么就产生对应的A1 A2 A3需求运算类,就比如上述的OperationAdd的相加方法,A1是直接相加,A2是直接相加后和别的数字再相加。

假如一个产品分为等级1和2,按产品等级区分,一个工厂生产等级1的产品,一个工厂生产等级2的产品,写法如下

public abstract class AbstractProductA{
    
}
public ProductA1 extend AbstractProductA{
    
}
public ProductA2 extend AbstractProductA{
    
}
//抽象工厂类  职责 定义每个工厂要实现的功能
public abstract class AbstractCreator{
    public abstract AbstractProductA createProductA();
    public abstract AbstractProductB createProductB();
}
​
public class Creator1 extend AbstractCreator{
    public AbstractProductA createProductA(){
        return new ProductA1();
    }
    public AbstractProductB createProductB(){
        return new ProductB1();
    }
}
​
public class Creator2 extend Creator{
     public AbstractProductA createProductA(){
        return new ProductA2();
    }
    public AbstractProductB createProductB(){
        return new ProductB2();
    }
}

在抽象工厂类中,如果有N个产品族,A B C .... 那么就需要创建N个创建产品方法 new ProductA1() 等等,其中Creator1 Creator2是实现共产类,如果有M个产品等级,就有M个Creator类。

可能看完上面写的还没明白为什么这样写?可以看一下如何调用的

AbstractCreator  creator1 = new Creator1();
AbstractCreator  creator2 = new Creator2();
AbstractProductA a1 = creator1.createProductA();
AbstractProcuctA a2 = creator2.createProductA();

可以看到在具体的业务中,不会有任何一个方法与实现类有关系,对于一个产品来说,我们只需要知道它的工厂方法,就可以直接产生一个产品对象。

回到之前对多工厂方法的缺点,新增一个算法需求,就增加一个运算类和运算工厂,这种写法很累。对比一下抽象工厂方法,可以发现新增产品C,Creator1和Creator2类里都需要增加一个createProductC()的方法,如果用多工厂类方法,就需要新增一个产品类,你可能会说违反了开闭原则,确实是,这个可以叫做有毒代码,扩展很难

3、使用场景

当一个产品族(或一组没有任何关系的对象),都有相同的约束,例如文本编辑器和一个图片编辑器,虽然都是编辑器,但是linux和window下的文本编辑器代码实现肯定是不同的,图片编辑器类似,这里相同约束就是操作系统类型,我们可以利用工厂模式,产生不同操作系统下的编辑器和图片处理。

也可以简单理解,为了不必在创建时显示指定要创建的类型,就考虑使用工厂模式,这其实可以说是工厂模式的本质了。

二、 建造者模式

1、建造者模式

也称生成器模式,模板如下

产品类

public class Product{
​
    public void doSomething(){
        //独立业务处理
    }
}
​

抽象建造者

public abstract class Builder{
    //设置产品的不同部分,以获得不同的产品
    public abstract void setPart();
    //建造产品
    public abstract Product buildProduct();
}

具体建造者

public class ConcreteProduct extends Builder{
​
    //设置产品
    public void setPart(){
        //产品内部逻辑处理
    }
    public Product buildProduct(){
        return product;
    }
    
}

构建产品

public class ProductBuilder{
    private Builder builder = new ConcretProduct();
    public Product buildAProduct(){
        builder.setPart();
        return builder.buildProduct();
    }
}

书上一般都这样介绍,但是实际上可以按照下面这种实现

public class Product {
    private String name;
    private int maxYear;
    private int produceYear;
​
    private Product() {
​
    }
​
    private Product(String name, int maxYear, int produceYear) {
        this.name = name;
        this.maxYear = maxYear;
        this.produceYear = produceYear;
    }
​
    public void doSomething() {
        //独立业务处理
    }
​
    public static class Builder {
        private String name;
        private int maxYear;
        private int produceYear;
        public Builder setName(String name) {
            this.name = name;
            return this;
        }
​
        public Builder setMaxYear(int maxYear) {
            this.maxYear = maxYear;
            return this;
        }
​
​
        public Builder setProduceYear(int produceYear) {
            this.produceYear = produceYear;
            return this;
        }
​
        public Product build() {
            //对参数进行判空或者其他处理
            return new Product(name, maxYear, produceYear);
        }
    }
​
}
​

调用如下

Product product = new Product.Builder().setName("产品").setMaxYear(2020).setProduceYear(2020).build();

2、优点和使用场景

建造者模式的优点在于它的独立性、封装性,客户端不需要知道产品内部组成的细节,不需要关心每一个模型背后的实现逻辑,对于第一种写法的,其Builder类的扩展性也很好的体现出来了。

与工厂模式都是创建类模式,但是建造者模式关注的更多是相同的方法,不同的方法的执行, 也就是如何组装成一个Product类,而工厂模式关注的重点是创建,组装Product类,组装顺序不是它关注的点。