天天看点

Android开发学习笔记之设计模式——工厂方法模式&抽象工厂模式Android开发学习笔记之设计模式——工厂方法模式&抽象工厂模式

文章目录

  • Android开发学习笔记之设计模式——工厂方法模式&抽象工厂模式
    • 简单工厂模式
      • 概述
      • 应用场景
      • 优缺点
      • 实现
    • 工厂方法模式
      • 概述
      • 应用场景
      • 优缺点
      • 实现
    • 抽象工厂模式
      • 概述
      • 应用场景
      • 优缺点
      • 实现
    • 总结

Android开发学习笔记之设计模式——工厂方法模式&抽象工厂模式

创建型模式的主要特点是“将对象的创建和使用分离”对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离,隐藏了类的实例的创建细节,通过隐藏对象如何被创建和组合在一起达到使整个系统独立的目的。为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不清楚其具体的实现细节,使整个系统的设计更加符合单一职责原则。

当我们在开发中,存在需要生成复杂对象的时候,即类的构造较为复杂时,此时如果我们将直接在业务代码中构造复杂对象,那么就容易造成业务代码中存在耦合,此时我们可以考虑使用工厂模式。工厂模式即定义一个专门用于构造对象的类,通过该类来构造复杂对象,从而“将对象的创建和使用分离”。

工厂模式可以分为简单工厂模式、工厂方法模式和抽象工厂模式。其中简单工厂模式不属于23种GOF设计模式之中。

简单工厂模式

概述

简单工厂模式不属于23种GOF设计模式,但是在开发过程中也属于一种常用设计模式。简单工厂模式又称为静态工厂方法模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

在工厂模式中,我们通常称构造的复杂对象为”产品“,把创建复杂对象的类称为”工厂“,在简单工厂模式中,我们通常会创建一个具体的工厂类,然后创建一个静态的方法用于创建”产品“,然后通过传入的参数类型来返回对应的”产品“对象。

应用场景

或许,我们可能会疑惑,根据简单工厂模式的描述和实现,我们会发现,简单工厂模式实际上就是将构造对象放入了一个专门的类中,在使用的时候我们还是需要根据不同的类型来获取不同的对象,那么这和我们直接通过创建一个对象有什么区别呢?有使用这个设计模式的必要吗?

答案是有这个必要的。因为一旦对象构造复杂,我们可能需要传递一系列参数,甚至有时我们在创建该对象时还需要先构造一系列的与业务代码不相关的对象,这种代码一旦多了之后就造成业务代码臃肿,同时也违反了

单一职责原则

。有可能在不同情况下,我们可能需要使用到同一抽象类的不同具体实现类,此时我们只需要知道所需类型即可,不关心不同对象的具体构造方式,将构造对象的代码直接写入业务代码显然是不合理的。这时,我们就可以考虑使用工厂模式了。

比如一个场景下,我们可能在需要在界面上使用一个圆形和方形的按钮,且二者都继承自一个按钮的基类,在创建两个对象之前,可能还有一些获取构造参数的步骤,此时我们就可以使用简单工厂模式。

对于产品种类相对较少的情况,考虑使用简单工厂模式。使用简单工厂模式的客户端只需要传入工厂类的参数,不需要关心如何创建对象的逻辑,可以很方便地创建所需产品。

优缺点

优点:

  • 工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品的实例。客户端可以免除直接创建产品对象的职责,很方便的创建出相应的产品。工厂和产品的职责区分明确。从而实现了”对象创建和使用分离“。
  • 客户端无需知道所创建具体产品的类名,只需知道参数即可。
  • 也可以引入配置文件,在不修改客户端代码的情况下更换和添加新的具体产品类。

缺点:

  • 简单工厂模式的工厂类单一,负责所有产品的创建,职责过重,一旦异常,整个系统将受影响。且工厂类代码会非常臃肿,违背高聚合原则。
  • 使用简单工厂模式会增加系统中类的个数(引入新的工厂类),增加系统的复杂度和理解难度。
  • 简单工厂模式使用了 static 工厂方法,造成工厂角色无法形成基于继承的等级结构.
  • 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。违背了

    开闭原则

实现

简单工厂模式实现方式较为简单,在简单工厂模式中,其结构主要分为以下三部分:

  • 工厂类:负责创建负责对象,通常会提供一个静态方法,然后根据请求类型返回具体对象。
  • 抽象产品类:产品类的抽象基类。
  • 具体产品类:工厂模式构建的具体产品对象。通常都继承自一个基类。

就拿上述的创建两个不同按钮为例,首先我们构建抽象产品类Button,其存在一个draw方法用于绘制按钮,并创建两个具体产品类,具体如下:

/**
 * 抽象产品类
 */
abstract class Button{

    init {
        draw()
    }

    abstract fun draw();

}

/**
 * 具体产品类
 */
class CircleButton : Button() {

    override fun draw() {
        println("draw a circle")
    }

}

/**
 * 具体产品类
 */
class RectButton : Button() {

    override fun draw() {
        println("draw a rect")
    }

}
           

然后,我们创建一个工厂类,用于构造Button对象,具体如下:

/**
 * 简单工厂模式
 */
class SimpleFactory {

    companion object{
        const val BUTTON_CIRCLE = "button_circle"
        const val BUTTON_RECT = "button_rect"

        /**
         * 构造具体产品对象的静态方法
         * @param type 产品类型
         * @return Button抽象类型
         */
        public fun createButton(type : String) : Button{
            return when(type){
                BUTTON_CIRCLE -> {
                    // TODO 可能某些用于构造对象的操作
                    CircleButton()
                }
                BUTTON_RECT -> {
                    // TODO 可能某些用于构造对象的操作
                    RectButton()
                }
                else -> throw Exception("错误的类型")
            }
        }
    }

}
           

如此一来,我们就完成了一个简单工厂模式的应用,当我们需要创建Button对象时,我们只需要传入对应的类型就可以获取到对应的Button对象,外部对如何创建Button对象是完全不关心的。如下:

val circle = SimpleFactory.createButton(SimpleFactory.BUTTON_CIRCLE)
val rect = SimpleFactory.createButton(SimpleFactory.BUTTON_RECT)
           

工厂方法模式

概述

从上文可知,对于简单工厂模式,如果我们需要新增一个产品类型,比如,我们又添加了一个圆角矩形的按钮,我们只需要在工厂类中添加一个对应的类型并在createButton方法中创建对应对象即可。但是,如果类型很多呢?那么我们就需要在createButton方法中添加大量代码,将所有Button创建都放于工厂类中,而且也违反了

开闭原则

,需要不断修改原方法。

而工厂方法模式就是对于简单工厂模式的进一步抽象,相比于简单工厂模式,工厂方法模式将工厂也进行抽象,然后对于每个具体产品类,我们都创建一个具体的工厂类,专门用于该产品的构造对象。这种抽象化的结果使这种结构可以在不修改具体工厂类的情况下引进新的产品,如果出现新的按钮类型,只需要为这种新类型的按钮创建一个具体的工厂类就可以获得该新按钮的实例,这一特点无疑使得工厂方法模式具有超越简单工厂模式的优越性,更加符合“开闭原则”。

应用场景

  • 当外部创建对象的类无法预知对象确切类别及其依赖关系时,可使用工厂方法。工厂方法模式将创建产品的代码与实际使用产品的代码分离, 从而能在不影响其他代码的情况下扩展产品创建部分代码。
  • 当我们希望用户可扩展内部组件时,可使用工厂方法。我们可以创建对应的抽象工厂类和产品类,当用户需要对组件进行扩展时,只需要继承抽象产品类实现具体产品,然后再构造其对应的工厂类即可。
  • 当我们创建对象时,希望可以复用之前的对象,也可以使用工厂方法模式。在处理大型资源密集型对象 (比如数据库连接、 文件系统和网络资源) 时,我们通常需要进行对象复用,但是如果我们将复用的代码写在业务逻辑代码中,那么显然是不合理的,因此我们可以使用工厂方法模式,将构造和复用对象的代码全部封装到工厂类中。

优缺点

优点:

  • 满足

    单一职责原则

    。 你可以将产品创建代码放在其对应的工厂类中, 从而使得代码更容易维护。
  • 满足

    开闭原则

    。 当需要扩展新的产品类型时,无需更改现有客户端代码, 只需要继承抽象产品类和工厂类创建对应的产品类和工厂类即可。
  • 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节。

缺点:

  • 应用工厂方法模式需要引入许多新的子类, 每个产品对需要创建对应的产品类和工厂类,代码可能会因此变得更复杂。 最好的情况是将该模式引入创建者类的现有层次结构中。
  • 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。

实现

与简单工厂模式相比,工厂方法模式抽象化程度更高,其结构相比而言也更加复杂一点,具体如下:

  • 抽象工厂(创建者)类:声明返回产品对象的工厂方法。 该方法的返回对象类型必须与产品接口相匹配。
  • 具体产品工厂(创建者)类:将会重写基础工厂方法, 使其返回不同类型的产品。
  • 抽象产品类:定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品类:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。

如下图所示:

Android开发学习笔记之设计模式——工厂方法模式&抽象工厂模式Android开发学习笔记之设计模式——工厂方法模式&抽象工厂模式

我们还是以之前的Button为例,其中产品类和具体产品类与简单工厂方法相同,我们所需要修改的就是工厂类,首先创建抽象工厂类,如下:

/**
 * 抽象工厂类
 */
abstract class ButtonFactory {

    /**
     * 抽象工厂方法,用于创建产品对象,返回类型为Button
     * 也可以不将其设为抽象方法,而是提供一个默认实现,子类去重写
     */
    abstract fun createButton():Button
    
}
           

抽象工厂提供了一个工厂方法,用于创建对应的产品对象,我们可以将其设为抽象方法

abstract

,也可以根据需求提供一个默认实现。然后对于每个具体的产品类,我们都需要去创建一个对应的工厂类,如下:

/**
 * 具体产品工厂类
 */
class CircleButtonFactory : ButtonFactory(){

    override fun createButton(): Button {
        //TODO 构建具体产品对象的逻辑
        return CircleButton()
    }

}

/**
 * 具体产品工厂类
 */
class RectButtonFactory : ButtonFactory(){

    override fun createButton(): Button {
        //TODO 构建具体产品对象的逻辑
        return RectButton()
    }

}
           

至此,工厂方法模式就实现完毕了,在之后的业务逻辑中,我们只需要调用对应的工厂类即可。值得注意的是,在具体的工厂类中,我们的工厂方法也应该返回Button类型,而不是具体的产品类型,因为这样在是使用工厂创建对象的位置就不会与产品类型耦合。

当然,这只是最简单的工厂方法模式的实现,实际上我们可以根据实际开发需求对其进行修改。比如工厂方法可以提供多个重载方法,根据不同的请求参数从而创建不同的产品对象,同时我们也可以通过工厂模式实现对产品对象的复用,在工厂类中持有一个对象池,当对象已存在时直接复用等。

一般来说,工厂对象应当有一个抽象的父类型,如果工厂等级结构中只有一个具体工厂类的话,抽象工厂就可以省略,也将发生了退化。当只有一个具体工厂,在具体工厂中可以创建所有的产品对象,并且工厂方法设计为静态方法时,工厂方法模式就退化成简单工厂模式。

抽象工厂模式

概述

在工厂方法模式中具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品,工厂方法也具有唯一性,一般情况下,一个具体工厂中只有一个工厂方法或者一组重载的工厂方法。但是有时候我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象。此时,我们又应该如何实现呢?这时,我们就可以使用抽象工厂模式了。

为了更好地理解抽象工厂模式,首先,我们需要了解两个概念:

  • **产品等级结构:**产品等级结构即产品的继承结构,如:手机是一个抽象类,但不同品牌地手机不同,因此其存在具体的实现类华为手机、小米手机、苹果手机等。这就构成了一个产品等级结构,其中手机为父类,具体不同品牌的手机为子类。
  • **产品族:**所谓产品族就是同一系列的不同产品,同一个工厂生产的,位于不同产品等级结构中的一组产品。比如:华为所生产的不仅是手机,还有电脑,平板等其它产品,这就是华为的产品族。

了解了产品族和产品等级结构后,我们就会发现,使用工厂方法模式,我们只能构造同一产品等级结构的产品对象,如果我们在开发中所需要创建的复杂对象,不仅是一个产品等级结构,而可能存在多个产品族,此时使用工厂方法模式是很难实现的。此时就需要使用抽象工厂模式了。

抽象工厂模式是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。

抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构,一个工厂等级结构可以负责多个不同产品等级结构中的产品对象的创建 。当一个工厂等级结构可以创建出分属于不同产品等级结构的一个产品族中的所有对象时,抽象工厂模式比工厂方法模式更为简单、有效率。

应用场景

  • 如果代码需要与多个不同系列的相关产品交互,但是由于无法提前获取相关信息,或者出于对未来扩展性的考虑,你不希望代码基于产品的具体类进行构建,在这种情况下,可以使用抽象工厂模式。抽象工厂为你提供了一个接口, 可用于创建每个系列产品的对象。 只要代码通过该接口创建对象, 那么你就不会生成与应用程序已生成的产品类型不一致的产品。
  • 如果一个类与多种类型产品交互, 就可以考虑将工厂方法抽取到独立的工厂类或具备完整功能的抽象工厂类中。

优缺点

优点:

  • 抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易。所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。
  • 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。
  • 增加一个产品族只需要增加一个工厂类即可,满足

    开闭原则

缺点:

  • 开闭原则

    的倾斜。增加一个产品族只需要增加一个具体工厂类即可,但增加一个产品等级结构困难,需要改变抽象类,不满足

    开闭原则

  • 由于采用该模式需要向应用中引入众多接口和类, 代码可能会比之前更加复杂。

实现

抽象工厂模式,其主要结构分为四个部分:

  • 抽象工厂:声明了一组创建各种抽象产品的方法,声明了一个产品族。
  • 具体工厂:继承了抽象工厂,代表了一个产品族,实现了该产品族各个产品对象的创建。
  • 抽象产品:为每种产品声明接口,在抽象产品中定义了产品的抽象业务方法。
  • 具体产品:定义具体工厂生产的具体产品对象,实现抽象产品接。口中定义的业务方法

其结构图如下:

Android开发学习笔记之设计模式——工厂方法模式&抽象工厂模式Android开发学习笔记之设计模式——工厂方法模式&抽象工厂模式

我们可以以上述的智能产品为例,存在华为和小米两个品牌,二者都生产手机和电脑。首先,我们创建各个抽象产品类Phone和Computer,以及其对应的具体产品,如下:

/**
 * 手机的抽象类
 */
abstract class Phone {

    abstract fun call()

}

/**
 * 华为手机,具体产品类
 */
class HuaWeiPhone : Phone(){

    override fun call() {
        //TODO 具体业务代码
        println("华为手机")
    }

}

/**
 * 小米手机,具体产品类
 */
class XiaoMiPhone : Phone(){

    override fun call() {
        //TODO 具体业务代码
        println("小米手机")
    }

}
           
/**
 * 电脑抽象产品类
 */
abstract class Computer {
    
    abstract fun play()
    
}

/**
 * 华为电脑,具体产品类
 */
class HuaWeiComputer : Computer() {
    
    override fun play() {
        //TODO 业务代码
        println("华为电脑")
    }

}

/**
 * 小米电脑,具体产品类
 */
class XiaoMiComputer : Computer() {

    override fun play() {
        //TODO 业务代码
        println("小米电脑")
    }

}
           

然后,创建抽象工厂,在其中声明手机和电脑的工厂方法,如下:

/**
 * 抽象工厂,声明创建产品的工厂方法
 */
abstract class ElectronicsFactory {

    /**
     * 创建手机的抽象工厂方法
     */
    abstract fun createPhone(): Phone

    /**
     * 创建电脑的抽象工厂方法
     */
    abstract fun createComputer(): Computer

}
           

最后,我们只需要继承抽象工厂,创建具体工厂类,实现工厂方法即可实现一个产品族,如下:

/**
 * 华为工厂,具体工厂
 */
class HuaWeiFactory : ElectronicsFactory() {

    /**
     * 华为手机的具体工厂方法
     */
    override fun createPhone(): Phone {
        //TODO 可能存在的逻辑
        return HuaWeiPhone()
    }

    /**
     * 华为电脑的具体工厂方法
     */
    override fun createComputer(): Computer {
        //TODO 可能存在的逻辑
        return HuaWeiComputer()
    }

}

/**
 * 小米工厂,具体工厂
 */
class XiaoMiFactory : ElectronicsFactory() {

    /**
     * 小米手机的具体工厂方法
     */
    override fun createPhone(): Phone {
        //TODO 可能存在的逻辑
        return XiaoMiPhone()
    }

    /**
     * 小米的具体工厂方法
     */
    override fun createComputer(): Computer {
        //TODO 可能存在的逻辑
        return XiaoMiComputer()
    }

}
           

至此,抽象工厂模式的简单应用就完成了,我们在需要创建产品对象时,我们只需要使用对应的工厂类,然后调用对应产品的工厂方法即可,而不必关心内部创建对象的逻辑。

总结

尽管从简单工厂模式、到工厂方法模式,再到抽象工厂模式,尽管三者是抽象程度越来越高,其中抽象工厂模式最为抽象和最具一般性的一种形态。,但是,我们在实际开发过程中,不是就一昧的使用抽象工厂模式,而是要根据实际情况来选择。因为抽象化程度越高,其代码量就会越多,类就越复杂,如果使用场景简单的话,我们可以直接使用简单工厂模式和工厂方法模式。

当抽象工厂模式中每一个具体工厂类只创建一个产品对象,也就是只存在一个产品等级结构时,抽象工厂模式退化成工厂方法模式;当工厂方法模式中抽象工厂与具体工厂合并,提供一个统一的工厂来创建产品对象,并将创建对象的工厂方法设计为静态方法时,工厂方法模式退化成简单工厂模式。所以,在只存在一个产品结构时,我们是完全可以使用简单工厂模式和工厂方法模式来替代的,即使使用抽象工厂模式也只会提高代码量,不能带来更多的优化效果。

设计模式,学习起来很简单,关键是需要在之后的开发中能够灵活运用,而且不同模式也能够相互配合,比如在工厂模式中,其实我们可以结合之前学习的单例模式,这样我们就不需要创建工厂对象了。