从功能迁移到OO的问题

Bru*_*una 14 oop haskell functional-programming scala

我习惯于使用函数式编程(主要是Haskell),我从OO(scala)开始.

我在翻译代码时遇到了麻烦.例如,那是我对H树的B树定义:

data BTree a = 
    Leaf
    |Node2 (BTree a) a (BTree a)
    |Node3 (BTree a) a (BTree a) a (BTree a)
    deriving (Eq,Read,Show) 
Run Code Online (Sandbox Code Playgroud)

这很简单.我的树是空的,或者它有一个值,是两棵树的父亲,或者是3棵子树的父亲.

OO是什么?我没有线索.我无法弄清楚我怎么能以理智的方式做到这一点.

Dan*_*ral 18

这里有一些很好的答案,但我认为他们都错过了展示你所缺少点的机会.所以,你已经证明了这一点:

data BTree a = 
    Leaf
    |Node2 (BTree a) a (BTree a)
    |Node3 (BTree a) a (BTree a) a (BTree a)
    deriving (Eq,Read,Show)
Run Code Online (Sandbox Code Playgroud)

并询问如何以面向对象的方式实现它.所以,这里是:

最重要的是

trait Tree[A] {
  // not required because it is inherited from AnyRef
  // def equals(other: Any): Boolean

  // not required because it is inherited from AnyRef
  // def toString: String

  // does not belong in the object
  // def fromString(input: String): Tree[A]

  // Stuff that is missing but is needed
  def isEmpty: Boolean
  def value: Option[A]
  def subtrees: Seq[Tree[A]]
  def iterator: Iterator[A]
  def depthFirstIterator: Iterator[A]
  def breadthFirstIterator: Iterator[A]
}
Run Code Online (Sandbox Code Playgroud)

所以,这就是交易:当你谈到面向对象时,你有一个BTree,一个手指树,或者其他任何树结构是不相关的.事实上,它应该是隐藏的.相关的是你可以用它什么.

你在这方面遇到了麻烦,因为你正是从你不应该的方向接近问题.

不那么重要的事情

sealed abstract class BTree[A] extends Tree[A]
object BTree {
  def apply[A](input: String): BTree[A] = { /* factory */ null.asInstanceOf[BTree[A]] }
  private case object Leaf extends BTree[Nothing] {
    // method implementation
  }
  private case class Node2[A](value: A, private one: BTree[A], private two: BTree[A]) extends BTree[A] {
    // method implementation
  }
  private case class Node3[A](value: A, private one: BTree[A], private two: BTree[A], private three: BTree[A]) extends BTree[A] {
    // method implementation
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,您实际上提供了一个实现,但BTree的详细信息是完全隐藏的.您只能使用Tree已定义的方法.

这是理想的面向对象架构:客户端依赖于接口,数据结构是隐藏的.


C. *_*ann 17

这是从功能性思维模式中获取OO的第一步:对象更像是函数而不是数据.想想他们就是这样; 而不是功能作用于透明的结构化数据,现在你有不透明的抽象行为.

从"好吧,这是我的数据的结构,现在我......"的角度来看它是向后的.

尝试这样的事情:

  • 首先弄清楚可以用你的B树做什么基本动作(不要忘记这里showfmap这里的事情)并根据这些来设计类.

  • 对于像树这样的和类,可以更容易地将基类留空,并为数据构造函数的不同变体使用子类.作为OO的经验法则,您必须做出某种选择以大幅改变后续行为,强烈考虑使用子类型多态来区分案例.

  • 在必要之前尽量不要担心内部表示,并且不要让表示细节从类中泄漏出来.拥有一堆返回原始类型的GetFoo()方法是"做错了"的标志.

最后:记住你正在使用Scala.出于某种原因,这是一种混合语言; 在OO风格中,并非一切都有意义.翻阅设计模式书,您会发现其中一半是关于缺少语言功能的巴洛克式高维护性解决方法.

  • +1"抽象行为的不透明整体" (2认同)
  • 翻阅"设计模式"一书的+1,您会发现其中一半是关于缺少语言功能的巴洛克式高维护性解决方案." 因为它几乎是圣诞节! (2认同)

Lan*_*nbo 12

由于您的标记列表中包含Scala,因此以下是在Scala中完成的操作:

你有一个基本特征(在Haskell中的类型),并从所有Haskell构造函数派生为case类.这样你也可以在Scala模式匹配中使用它们.

sealed trait Tree[+A]

case object Leaf extends Tree[Any]
case class Node2[+A](a: A,  t1: Tree[A], t2: Tree[A]) extends Tree[A]
case class Node3[+A](a: A, b: A,  t1: Tree[A], t2: Tree[A], t2: Tree[A]) extends Tree[A]
Run Code Online (Sandbox Code Playgroud)

在Java(自1.5),C++和C#等语言中,您可以使用相同类型的模板来帮助键入安全性.它们基本上像Haskell中的类型变量一样工作.

此示例位于Scala中,但对于其他OO语言,您可以采用类似的方式执行此操作:创建抽象基类并将数据的构造函数转换为类/对象.

  • OOP的基本概念是封装和动态调度子类型多态性; 最小或有状态与它无关.案例类的*全部目的*是快速,轻松地揭示其内部表示.这基本上是做OOP的*相反*.我并不是说这是一个糟糕的事情 - 严肃地说,只要看看我的回答历史,看看我自己的偏好在哪里 - 这只是因为你基本上是在告诉Bruna学习OOP ...与Haskell完全相同,只是语法不同. (7认同)
  • 那不是我要表达的意思.仅仅因为它们被称为类和对象并不能成为OO; 记住那些可以用任何语言编写FORTRAN的程序员的老笑话.没有通过模式匹配提取的自己保持数据的逻辑的类对于所有实际目的而言都是类似于Haskell的代数数据类型,无论涉及什么语法糖. (6认同)
  • 那真的是OO风格吗?我不太了解Scala,但它是一种混合的FP/OO语言,我认为像你在这里使用的案例类只是将Haskell代码直接转换为FP风格的Scala,而不是OO. (4认同)
  • 它从未说过这些课程中没有逻辑,真的.您可以将`Tree`函数作为方法添加到Tree特征中,或者编写单例来处理它们.此外,Object是有状态数据结构,并且不需要保持行为以成为对象.在OOP中,每个类/对象的状态和行为都应该是最小的. (2认同)