欢迎使用OOP模式匹配:访客模式

访客模式已死。 访客模式万岁。 (以Kotlin为例)

函数式编程在IT领域获得了巨大的发展。 事情来来往往,但FP并不是其中之一。 比OOP更具表现力

欢迎使用OOP模式匹配:访客模式_第1张图片

几年前,我在博洛尼亚举行的LambaConf会议上开始研究它,我获得的见识越多,我就越喜欢FP。 在六月,我去了这个工作坊,老师深入研究了代数数据类型模式匹配 我最终也了解了Monad是什么,但这是另一个故事。 我曾经将模式匹配视为一种解构列表的有趣方式。 现在,我知道这是FP软件设计的基础,并且是解决将域对象数据和行为分离的有效方法。

让我们来看一个例子!

按国家/地区本地化的ERP

假设我们要编写一个ERP软件,以竞争两个不同国家的市场。 意大利和德国。 我们将在许多域对象上编写许多CRUD操作。 这里没有挑战。

但是,如何在相同的功能流内在不同的数据结构上针对特定国家/地区的业务规则建模呢?

例如,这两个国家/地区的数据库中都有文章,并且您肯定要在它们上进行搜索。 但是搜索规则和获取的数据可能完全不同。

由于数据结构不同,我们不能仅仅拥有域实体“ Article”:我们至少需要一个“ ItalianArticle”和“ GermanArticle”。 考虑到数据存储的结构也可能不同。

FP方式

让我们看一下Scala的实现:写一个sum类型的Article 然后我们将其专门化为ItalianArticleGermanArticle的产品类型。 另外,我们希望当我们搜索某些内容时,有时什么也找不到。 因此,我们还将考虑ArticleNotFound

sealed trait Article
case class ItaArticle ( id: Int , itaData1: String , itaData2: Int ) extends Article
case class DeuArticle ( id: Int , deuData1: Int , deuData2: Int ) extends Article
case class ArticleNotFound ( ) extends Article

每次我们从某个地方收到文章时,我们都会在其上进行图案匹配。 在比赛中,我们可以访问其专门数据

def doSomething (article: Article ) : Unit = {
  article match {
    case ita: ItaArticle => print(ita.itaData1)
    case deu: DeuArticle => print(deu.deuData2)
    case nf: ArticleNotFound => print( "None" )
  }
}

不是开关吗?

乍一看,它看起来像是经典的程序“开关”,然后是向下转换。 有一个重要的区别:编译器知道我们是否匹配每种类型的Article。 例如,如果我们忘记在ArticleNotFound上进行匹配

def doSomething (article: Article ) : Unit = {
  article match {
    case ita: ItaArticle => print(ita.itaData1)
    case deu: DeuArticle => print(deu.deuData2)
  }
}

那么默认情况下, sbt编译器将发出警告。

欢迎使用OOP模式匹配:访客模式_第2张图片

编译器知道有问题时,我们可以使它引发错误而不是警告。

新要求:西班牙

欢迎使用OOP模式匹配:访客模式_第3张图片

此时,完全出乎意料的事情发生了! 业务对我们提出了新要求:我们还需要在西班牙进行分销。

其实我们已经准备好了,所以让我们添加SpaArticle   我们的Article和类型。

sealed trait Article
case class ItaArticle ( id: Int , itaData1: String , itaData2: Int ) extends Article
case class DeuArticle ( id: Int , deuData1: Int , deuData2: Int ) extends Article
case class SpaArticle ( id: Int , spaData1: Float , spaData2: String ) extends Article
case class ArticleNotFound ( ) extends Article

现在,我们需要添加西班牙语业务逻辑。

如果正常切换,搜索在每个实施了国家特定业务规则的地方将很痛苦。

相反,使用模式匹配,编译器会告诉我们必须进行干预。

OOP有可能吗?

是的,当然可以使用“ 访客”模式

欢迎使用OOP模式匹配:访客模式_第4张图片

如果您不了解访问者模式 ,请在此处查看

如果您不知道四个帮派的话,请在这里看看。

访客模式作为模式已经很久了 当您添加新的项目类型时,您还将向访问者界面添加新的方法。 在这种情况下,编译器将中断每个具体的访问者,直到您在每个访问者中实现新方法为止。 这是成为反模式的原因之一

实际上,对于我们的担忧,这个“问题”看起来与我们正在寻找的完全一样!

Kotlin示例

在此遵循我们的总和类型的Kotlin实现:

interface Article {
    fun applyTo (consumer: ArticleConsumer )
}

class ItaArticle ( val itaData1: String, val itaData2: Int ) : Article {
    override fun applyTo (consumer: ArticleConsumer ) {
        consumer.use( this )
    }
}

class DeuArticle ( val deuData1: String) : Article {
    override fun applyTo (consumer: ArticleConsumer ) {
        consumer.use( this )
    }
}

您会注意到,我更喜欢考虑“应用于数据使用者的数据”,而不是使用“ accept”和“ Visitor”命名。 (我正在寻找更好的命名方式,因此,如果您有任何建议,请发表评论!谢谢

在这里,我们可以实施特定国家/地区的业务规则

interface ArticleConsumer  {
    fun use (article: ItaArticle )
    fun use (article: DeuArticle )
    fun use (article: ArticleNotFound )
}

fun useAnArticle (article: Article ) : String {
    var x = ""

    article.applyTo( object : ArticleConsumer {
        
        override fun use (article: ItaArticle ) {
            x = article.itaData1
        }

        override fun use (article: DeuArticle ) {
            x = article.deuData1
        }

        override fun use (article: ArticleNotFound ) {
            x = "not found"
        }

    })

    return x
}

此示例的语义与FP模式匹配完全相同。 当我们要添加新的国家,那么编译器是要打破一切,直到我们不执行新的fun use(article: SpaArticle)在每一个ArticleConsumer

结论

访客模式的最初目的是对异构对象的集合进行迭代操作,该异构对象不共享相同的接口和数据类型。

在本文中,我建议将其用作路由点。 当您最终枚举域实体并需要设置本地化的域上下文时,此功能很有用。

在方法中枚举域实体被认为是一个问题。 在这种用例中,它是模式的关键特征。

我还证明了这种用法在语义上等同于FP模式匹配

请在评论中留下您的意见和反馈。

感谢您的阅读!

From: https://hackernoon.com/welcome-to-the-oop-pattern-matching-visitor-pattern-1q7n031xc

你可能感兴趣的:(欢迎使用OOP模式匹配:访客模式)