前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >设计模式-访问者模式

设计模式-访问者模式

作者头像
Anymarvel
发布2018-10-22 11:11:43
4280
发布2018-10-22 11:11:43
举报
文章被收录于专栏:Android开发实战Android开发实战

以简单的module学习设计模式

前言

访问者模式是一种将数据操作与数据结构分离的设计模式。 使用场景:

  1. 主要解决稳定的数据结构和易变的操作耦合问题。
  2. 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,使用访问者模式将这些封装到类中。即数据结构不变,数据发生改变适用的设计模式

基本思路

访问者模式的基本想法是,软件系统中拥有一个由许多对象构成的、比较稳定的对象结构,这些对象的类都拥有一个 accept 方法用来接受访问者对象的访问。访问者是一个接口,它拥有一个 visit 方法,这个方法对访问到的对象结构中不同类型的元素做出不同的处理。在对象结构的一次访问过程中,我们遍历整个对象结构,对每一个元素都实施 accept 方法,在每一个元素的 accept 方法中会调用访问者的 visit 方法,从而使访问者得以处理对象结构的每一个元素,我们可以针对对象结构设计不同的访问者类来完成不同的操作,达到区别对待的效果。

UML图

  • Visitor:接口或者抽象类,它定义了对每一个元素(Element)访问的行为,它的参数就是可以访问的元素,它的方法数理论上来讲与元素个数是一样的,因此,访问者模式要求元素的类族要稳定,如果经常添加、移除元素类,必然会导致频繁地修改Visitor接口,如果这样则不适合使用访问者模式。
  • ConcreteVisitor1、ConcreteVisitor2:具体的访问类,它需要给出对每一个元素类访问时所产生的具体行为。
  • Element:元素接口或者抽象类,它定义了一个接受访问者的方法(Accept),其意义是指每一个元素都要可以被访问者访问。
  • ConcreteElementA、ConcreteElementB:具体的元素类,它提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
  • ObjectStructure:定义当中所说的对象结构,对象结构是一个抽象表述,它内部管理了元素集合,并且可以迭代这些元素供访问者访问。

访问者模式(账本问题)

我们都知道财务都是有账本的,这个账本就可以作为一个对象结构,而它其中的元素有两种,收入和支出,这满足我们访问者模式的要求,即元素的个数是稳定的,因为账本中的元素只能是收入和支出。

而查看账本的人可能有这样几种,比如老板,会计事务所的注会,财务主管,等等。而这些人在看账本的时候显然目的和行为是不同的。

首先我们给出单子的接口,它只有一个方法accept。

代码语言:javascript
复制
/**
 * 创建一个账单接口,有接收访问者的功能
 */interface Bill {
    fun accept(accountBookView: AccountBookView)
}

其中的方法参数AccountBookView是一个账本访问者接口,接下来也就是实现类,收入单子和消费单子,或者说收入和支出类。

代码语言:javascript
复制
/**
 * 消费单子
 */class ConsumerBill : Bill {
    internal var item: String = ""
    internal var amount: Double = 0.0

    internal constructor(item: String, amount: Double) {        this.amount = amount        this.item = item
    }

    fun getItem(): String {        return item
    }

    fun getAmount(): Double {        return amount
    }

    override fun accept(accountBookView: AccountBookView) {
        accountBookView.view(this)
    }
}
代码语言:javascript
复制
/**
 * 支出单子
 */class IncomeBill : Bill {    private var item: String = ""
    internal var amount: Double = 0.0

    internal constructor(item: String, amount: Double) {        this.item = item        this.amount = amount
    }

    fun getItem(): String {        return item
    }

    fun getAmount(): Double {        return amount
    }

    override fun accept(accountBookView: AccountBookView) {
        accountBookView.view(this)
    }
}

上面最关键的还是里面的accept方法,它直接让访问者访问自己,这相当于一次静态分派,当然我们也可以不使用重载而直接给方法不同的名称。

接下来是账本访问者接口

代码语言:javascript
复制
/**
 * 访问者接口
 */interface AccountBookView {
    fun view(consumerBill: ConsumerBill)

    fun view(incomeBill: IncomeBill)
}

这两个方法是重载方法,就是在上面的元素类当中用到的,当然你也可以按照访问者模式类图当中的方式去做,将两个方法分别命名为viewConsumeBill和viewIncomeBill,而一般建议按照类图上来做的

访问者的实现

代码语言:javascript
复制
/**
 * 老板类:访问者是老板,主要查看总支出和总收入
 */class Boss : AccountBookView {    private var totalConsumer: Double = 0.toDouble()    private var totalIncome: Double = 0.toDouble()    // 查看消费的单子
    override fun view(consumerBill: ConsumerBill) {
        totalConsumer += consumerBill.amount
    }    // 查看收入单子
    override fun view(incomeBill: IncomeBill) {
        totalIncome += incomeBill.amount
    }

    internal fun getTotalConsumer() {
        println("老板查看到 一共消费了$totalConsumer")
    }

    internal fun getTotalIncome() {
        println("老板查看到 一共收入了$totalIncome")
    }

}
代码语言:javascript
复制
/**
 * 会计类:访问者是会计,主要记录每笔单子
 */class CPA : AccountBookView {    private var count = 0

    // 查看消费的单子
    override fun view(consumerBill: ConsumerBill) {
        count++        if (consumerBill.getItem() == "消费") {
            println("第" + count + "个单子消费了:" + consumerBill.getAmount())
        }
    }    // 查看收入单子
    override fun view(incomeBill: IncomeBill) {        if (incomeBill.getItem() == "收入") {
            println("第" + count + "个单子收入了:" + incomeBill.getAmount())
        }
    }
}

老板只关心收入和支出的总额,而注会只关注该交税的是否交税

接下来是账本类,它是当前访问者模式例子中的对象结构

代码语言:javascript
复制
/**
 * 账单类:用于添加账单,和为每一个账单添加访问者
 */internal class AccountBook {    private val listBill = ArrayList<Bill>()    // 添加单子
    fun add(bill: Bill) {
        listBill.add(bill)
    }    // 为每个账单添加访问者
    fun show(viewer: AccountBookView) {        for (b in listBill) {
            b.accept(viewer)
        }
    }
}

账本类当中有一个列表,这个列表是元素(Bill)的集合,这便是对象结构的通常表示,它一般会是一堆元素的集合,不过这个集合不一定是列表,也可能是树,链表等等任何数据结构,甚至是若干个数据结构。其中show方法,就是账本类的精髓,它会枚举每一个元素,让访问者访问。

测试客户端

代码语言:javascript
复制
object VisitorTest {    @JvmStatic
    fun main(args: Array<String>) {        // 创建消费和收入单子
        val consumerBill = ConsumerBill("消费", 3000.0)
        val incomeBill = IncomeBill("收入", 5000.0)
        val consumerBill2 = ConsumerBill("消费", 4000.0)
        val incomeBill2 = IncomeBill("收入", 8000.0)        // 添加单子
        val accountBook = AccountBook()
        accountBook.add(consumerBill)
        accountBook.add(incomeBill)
        accountBook.add(consumerBill2)
        accountBook.add(incomeBill2)        // 创建访问者
        val boss = Boss()
        val cpa = CPA()////        // 接受 访问者
        accountBook.show(boss)
        accountBook.show(cpa)//        // boss查看总收入和总消费
        boss.getTotalConsumer()
        boss.getTotalIncome()
    }
}

上面的代码中,可以这么理解,账本以及账本中的元素是非常稳定的,这些几乎不可能改变,而最容易改变的就是访问者这部分。

访问者模式最大的优点就是增加访问者非常容易,我们从代码上来看,如果要增加一个访问者,你只需要做一件事即可,那就是写一个类,实现AccountBookViewer接口,然后就可以直接调用AccountBook的show方法去访问账本了。

如果没使用访问者模式,一定会增加许多if else,而且每增加一个访问者,你都需要改你的if else,代码会显得非常臃肿,而且非常难以扩展和维护。

结果:

以最简单的module学习设计模式

代码实现仓库: https://github.com/AnyMarvel/desigPattern

设计模式持续更新中:https://www.jianshu.com/p/e3c25095c31f 持续更新中

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-06-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Android历练记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档