在面向对象编程中创建一个对象通常通过new关键字来创建,但是往往在一些业务场景下,个别对象是一个比较复杂的bean。此时“创建对象”不光是new了,还需要一些额外的操作,比如填充数据,附属对象的准备等等。如果我们想要得到这样的一个对象,直接简单粗暴的在需要的时候,创建、准备数据。。。做这些操作的话,那我么你的代码将臃肿不堪,到处充斥着业务逻辑,业务需求变化时,就GG了。我们身为高贵的软件工程师,必须保持着代码洁癖,如何提升代码质量呢?那就运用到今天的主角:工厂模式。


介绍

工厂模式是代替new来创建对象的一种设计模式,作用是降低代码耦合度,提升程序的易扩展性。

分类

  1. 简单工厂模式
  2. 工厂方法模式
  3. 抽象工厂模式

三种模式都是工厂模式,他们都具有工厂模式的优点,抽象程度从上到下越来越高,分别都有不同的适用场景,接下来逐一介绍。

一、简单工厂模式

简单工厂的角色分为3个

  • 工厂
  • 抽象产品
  • 具体产品

举例:
比如我正在“吃鸡”,跳伞刚落地,急需一把枪防身.
有如下类

public abstract class Gun {
    private String name;

    public Gun(String name) {
        this.name = name;
    }
    
    public void shoot(){
        System.out.println(name);
    }
}


public class M416 extends Gun {

    public M416() {
        super("m416");
    }
}

public class Ak47 extends Gun {

    public Ak47() {
        super("ak47");
    }
}

没有使用工厂模式时代码是这样的

Gun gun = new Ak47();
或
Gun gun = new M416();

使用了工厂模式时

public class SimpleFactory {
    public static Gun getGun(GunType gunType){
        switch (gunType){
            case AK47:
                return new Ak47();
            case M416:
                return new M416();
            default:
                return null;
        }
    }

    public enum GunType{
        AK47,M416;
    }
}

客户端调用:

public class SimpleFactoryClient {

    public static void main(String[] args) {
        //得到ak47
        Gun gun = SimpleFactory.getGun(SimpleFactory.GunType.AK47);
        
        //得到m416
        gun = SimpleFactory.getGun(SimpleFactory.GunType.M416);
    }
}

这样代码就清晰多了,这就好比没用工厂模式时自己要冒险去找枪,需要知道业务的细节。使用了工厂模式,只需要大喊一声我要m4,就有快递员把枪送到你面前,而不需要知道获得枪的过程。

这就是简单工程模式,简单工程模式确实将代码解耦了,但是如果我们想要awm呢?需要增加一种枚举,然后多加一个case语句,这是不符合设计模式的开闭原则(对扩展开放,对修改关闭)。接下来我们介绍下一种,工厂方法模式。

二、工厂方法模式

工厂方法的角色分为4个

  • 抽象工厂
  • 具体工厂
  • 抽象产品
  • 具体产品

举例:

之前简单工厂中使用了switch case来判断到底要返回哪个实例,此时我们可以将switch case给代替。
我们定义一个抽象的枪工厂

public interface GunFactory {
    Gun getGun();
}

然后分别定义具体的ak47工厂和m416工厂

public class Ak47Factory implements GunFactory {
    @Override
    public Gun getGun() {
        return new Ak47();
    }
}

public class M416Factory implements GunFactory {
    @Override
    public Gun getGun() {
        return new M416();
    }
}

客户端调用:

public class FactoryClient {
    public static void main(String[] args) {
        //获取ak47
        GunFactory gunFactory = new Ak47Factory();
        Gun gun = gunFactory.getGun();

        //获取m416
        gunFactory = new M416Factory();
        gun = gunFactory.getGun();
    }
}
当我们需要获得awm时,则只需要新增一个Awm类和一个AwmFactory让其实现GunFactory即可,这样避免了写switch case,符合了开闭原则。

此时有的小伙伴可能会问,最初不适用工厂方法时是用new Ak47()来获得枪,现在使用new Ak47Factory()不是和之前一样吗?说明下,这是我假设的ak47可能仅仅只是需要new以下,但在我们实际的场景中,有些bean的创建是及其复杂,他可能是其他开发人员负责的逻辑,你去对接时根本不需要了解其中的细节,只需要获得仅此而已。工厂模式就是封装了构建产品的细节,客户端根据想要的产品,选择对应的工厂就可获得相应的产品。

我们现在得到了枪,但是还没有子弹和配件,此时我们想要直接得到一把满配的枪。
但是子弹的类型有多种,配件的类型也有多种,此时我们又想到了为子弹和配件创建工厂,但是我们获得一把枪满配的枪并不需要知道怎么获得子弹和配件,所以我们的枪工厂就要直接包含子弹工厂和配件工厂,因此我们的抽象工厂模式呼之欲出。

三、抽象工厂模式

抽象工厂的角色分为4个

  • 抽象工厂
  • 具体工厂
  • 抽象产品(多个相同主题)
  • 具体产品

举例:
首先我们创建子弹的类

public abstract class Bullet {
    protected String name;

    public Bullet(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

public class Ak47Bullet extends Bullet {

    public Ak47Bullet() {
        super("ak的子弹");
    }
}

public class M416Bullet extends Bullet {

    public M416Bullet() {
        super("m416的子弹");
    }
}

接着是子弹工厂

public interface BulletFactory {
    Bullet getBullet();
}

public class Ak47BulletFactory implements BulletFactory {
    @Override
    public Bullet getBullet() {
        return new Ak47Bullet();
    }
}

public class M416BulletFactory implements BulletFactory {
    @Override
    public Bullet getBullet() {
        return new M416Bullet();
    }
}

配件的代码与其相同只不过是名字换一个,此时是不是发现我们的子弹工厂和我们之前实现的枪工厂其实是一模一样的。我们都使用了工厂方法模式,但是我们最终想要获得是完整的满配的枪,即枪中包含了子弹和配件。所以抽象工厂模式就将一系列具有相同主题的工厂封装在一起。
因此抽象工厂解决的范畴是产品族等级(完整的枪),工厂方法模式解决的范畴是产品等级(子弹、配件)。

枪工厂

public class Ak47Factory implements GunFactory {
    @Override
    public Gun getGun() {
        Bullet bullet = new Ak47Bullet();
        return new Ak47(bullet);
    }
}

public class Ak47 extends Gun {

    public Ak47(Bullet bullet) {
        super("ak47");
        this.setBullet(bullet);
    }
}

public abstract class Gun {
    private String name;

    private Bullet bullet;

    public Gun(String name) {
        this.name = name;
    }

    public void shoot(){
        System.out.println(name + "正在使用" + bullet.getName() + "射击");
    }

    public Bullet getBullet() {
        return bullet;
    }

    public Gun setBullet(Bullet bullet) {
        this.bullet = bullet;
        return this;
    }
}

客户端调用

public class FactoryClient {
    public static void main(String[] args) {
        //获取ak47
        GunFactory gunFactory = new Ak47Factory();
        Gun gun = gunFactory.getGun();
        System.out.println(gun.getBullet());//ak的子弹

        //获取m416
        gunFactory = new M416Factory();
        gun = gunFactory.getGun();
        System.out.println(gun.getBullet());//m416的子弹
    }
}

总结

三种工厂方法都有各自的优缺点,也有各自的试用场景

简单工厂方法:工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅"消费"产品。耦合度低。明确区分了各自的职责和权力,有利于整个软件体系结构的优化。

工厂方法模式:工厂方法模式是为了克服简单工厂模式的缺点。简单工厂模式的工厂类随着产品类的增加需要增加很多方法(或代码),而工厂方法模式每个具体工厂类只完成单一任务,代码简洁。工厂方法模式完全满足OCP,即它有非常良好的扩展性。

抽象工厂模式:抽象工厂模式主要在于应对“新系列”的需求变化。分离了具体的类,一个抽象工厂创建了一个完整的产品系列,所以整个产品系列会立刻改变。它有利于产品的一致性。是对多个构成产品的“零件”工厂的封装,使一个切换产品主题变得极为容易。

其实在我们日常做需求时,代码的架构可能是一步一步演化的,最开始接到需求可能就是连三种产品,这是我们可能就设计成简单工厂模式。之后产品增加多了,而且其他开发人员可能也要处理这边的逻辑,则原先的switch case很有可能就会成为bug的伏笔。此时架构便可以升级为工厂方法模式。再后来原来的产品变得庞大且复杂,需要将产品设计成多个零件构成,此时架构又可以升级成抽象工厂模式,将耦合度进一步降低,之后其他的开发者想要新增产品,一看结构清晰明了,便可以高质量的完成工作,早早下班回家。

设计模式帮助我们提升代码质量,每种设计模式都有其适合的场景,切勿过度设计,让技术推动业务,不定时重构代码,不断构造更好的自己。

欢迎各路大神不吝赐教。


请叫我程序猿大人
19 声望2 粉丝