设计模式系列----简单工厂模式、工厂模式、抽象工厂模式理解

设计模式是阅读源码以及学习优秀开源项目的必备,不然当我们去阅读这些框架源码的时候会发现无从下手,因为类太多了,关系太复杂,而且很多类的命名看不懂,比如
xxxBuilder 、xxxStrategy 、xxxFilter 等,一个词看不懂就可能导致你直接放弃继续阅读。这就是我尝试看netty源码以后,决定先学设计模式的根本原因了,塔喵太复杂了

在平时编程中,构建对象最常用的方式是 new 一个对象。乍一看这种做法没什么不好,而实际上这也属于一种硬编码。每 new 一个对象,相当于调用者多知道了一个类,增加了类与类之间的联系,不利于程序的松耦合。其实构建过程可以被封装起来,工厂模式便是用于封装对象的设计模式。

1.1.简单工厂模式

举个例子,直接 new 对象的方式相当于当我们需要一个苹果时,我们需要知道苹果的构造方法,需要一个梨子时,需要知道梨子的构造方法。更好的实现方式是有一个水果工厂,我们告诉工厂需要什么种类的水果,水果工厂将我们需要的水果制造出来给我们就可以了。这样我们就无需知道苹果、梨子是怎么种出来的,只用和水果工厂打交道即可。
水果工厂

public class FruitFactory {
    public Fruit create(String type) {
        switch (type) {
            case "苹果":
                return new Apple();
            case "梨子":
                return new Pear();
            default:
                throw new IllegalArgumentException("暂时没有这种水果");
        }
    }
}

调用者

public class User {
    private void eat(){
        FruitFactory fruitFactory = new FruitFactory();
        Fruit apple = fruitFactory.create("苹果");
        Fruit pear = fruitFactory.create("梨子");
        apple.eat();
        pear.eat();
    }
}

事实上,将构建过程封装的好处不仅可以降低耦合,如果某个产品构造方法相当复杂,使用工厂模式可以大大减少代码重复。比如,如果生产一个苹果需要苹果种子、阳光、水分,将工厂修改如下:

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("暂时没有这种水果");
        }
    }
}

调用者的代码则完全不需要变化,而且调用者不需要在每次需要苹果时,自己去构建苹果种子、阳光、水分以获得苹果。苹果的生产过程再复杂,也只是工厂的事。这就是封装的好处,假如某天科学家发明了让苹果更香甜的肥料,要加入苹果的生产过程中的话,也只需要在工厂中修改,调用者完全不用关心。

不知不觉中,我们就写出了简单工厂模式的代码。工厂模式一共有三种:

简单工厂模式
工厂方法模式
抽象工厂模式

注:简单工厂模式并不是23中设计模式中的一种,其被划分为工厂方法模式的一种特例,没有单独被列出来。

总而言之,简单工厂模式就是让一个工厂类承担构建所有对象的职责。调用者需要什么产品,让工厂生产出来即可。它的弊端也显而易见:

           一是如果需要生产的产品过多,此模式会导致工厂类过于庞大,承担过多的职责,变成超级类。当苹果生产过程需要修改时,要来修改此工厂。梨子生产过程需要修改时,也要来修改此工厂。也就是说这个类不止一个引起修改的原因。违背了单一职责原则。
           二是当要生产新的产品时,必须在工厂类中添加新的分支。而开闭原则告诉我们:类应该对修改封闭。我们希望在添加新功能时,只需增加新的类,而不是修改既有的类,所以这就违背了开闭原则。

1.2.工厂方法模式

为了解决简单工厂模式的这两个弊端,工厂方法模式应运而生,它规定每个产品都有一个专属工厂。比如苹果有专属的苹果工厂,梨子有专属的梨子工厂,Java 代码如下:

苹果工厂:

public class AppleFactory {
    public Fruit create(){
        return new Apple();
    }
}

梨子工厂:

public class PearFactory {
    public Fruit create(){
        return new Pear();
    }
}

调用者:

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();
    }
}

有读者可能会开喷了,这样和直接 new 出苹果和梨子有什么区别(同道中人,我之前就一直迷茫这和直接new出来有什么区别)?
在这里插入图片描述
上文说工厂是为了减少类与类之间的耦合,让调用者尽可能少的和其他类打交道。用简单工厂模式,我们只需要知道 FruitFactory,无需知道 Apple 、Pear 类,很容易看出耦合度降低了。但用工厂方法模式,调用者虽然不需要和 Apple 、Pear 类打交道了,但却需要和 AppleFactory、PearFactory 类打交道。有几种水果就需要知道几个工厂类,耦合度完全没有下降啊,甚至还增加了代码量!

这位读者请先放下手中的大刀,仔细想一想,工厂模式的第二个优点在工厂方法模式中还是存在的。当构建过程相当复杂时,工厂将构建过程封装起来,调用者可以很方便的直接使用,同样以苹果生产为例:

public class AppleFactory {
    public Fruit create(){
        AppleSeed appleSeed = new AppleSeed();
        Sunlight sunlight = new Sunlight();
        Water water = new Water();
        return new Apple(appleSeed, sunlight, water);
    }
}

调用者无需知道苹果的生产细节,当生产过程需要修改时也无需更改调用端。同时,工厂方法模式解决了简单工厂模式的两个弊端。

当生产的产品种类越来越多时,工厂类不会变成超级类。工厂类会越来越多,保持灵活。不会越来越大、变得臃肿。如果苹果的生产过程需要修改时,只需修改苹果工厂。梨子的生产过程需要修改时,只需修改梨子工厂。符合单一职责原则。
当需要生产新的产品时,无需更改既有的工厂,只需要添加新的工厂即可。保持了面向对象的可扩展性,符合开闭原则。

1.3.抽象工厂模式

工厂方法模式可以进一步优化,提取出工厂接口:

public interface IFactory {
    Fruit create();
}

然后苹果工厂和梨子工厂都实现此接口:

public class AppleFactory implements IFactory {
    @Override
    public Fruit create(){
        return new Apple();
    }
}
public class PearFactory implements IFactory {
    @Override
    public Fruit create(){
        return new Pear();
    }
}

此时,调用者可以将 AppleFactory 和 PearFactory 统一作为 IFactory 对象使用,调用者 Java 代码如下:

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();
    }
}

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

由于客户端只和 IFactory 打交道了,调用的是接口中的方法,使用时根本不需要知道是在哪个具体工厂中实现的这些方法,这就使得替换工厂变得非常容易。

例如:

public class User {
    private void eat(){
        IFactory factory = new AppleFactory();
        Fruit fruit = factory.create();
        fruit.eat();
    }
}

如果需要替换为吃梨子,只需要更改一行代码即可:

public class User {
    private void eat(){
        IFactory factory = new PearFactory();
        Fruit fruit = factory.create();
        fruit.eat();
    }
}

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

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



----我是“道祖且长”,一个在互联网“苟且偷生”的Java程序员

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三七有脾气

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值