你在问一些很好的问题。如何设计、构建和实现程序,以及如何对问题域进行建模。
OOP、“方法”和方法
您提出的问题表明您已了解 OOP(面向对象编程)。在许多关于 OOP 的介绍性材料中,通常鼓励通过对象直接对问题域进行建模,并通过向它们添加方法来实现功能的子类型化和实现。一个经典的例子是为动物建模,例如一个Animal 类型和两个子类型Duck 和Cat,并实现功能,例如walk、quack 和mew。
直接使用对象和子类型对问题域建模是有意义的,但与简单地使用具有不同字段的单个或几个类型来描述它是什么相比,它也可能非常过分和麻烦.在您的情况下,我确实相信像您使用对象和子类型或替代方法这样更复杂的建模是有意义的,因为在其他方面,您的功能取决于类型以及有些复杂的数据(例如带有存货)。但要记住这一点 - 存在不同的权衡,有时,使用具有多个不同字段的单一类型来对域进行建模总体上更有意义。
实现通过基类和子类型上的方法实现所需的功能同样有不同的权衡,对于给定的情况,这并不总是一个好的方法。对于您的一个问题,您可以执行类似添加print 方法或类似于基本类型和每个子类型的操作,但这在实践中并不总是那么好(一个简单的示例是简化算术的计算器应用程序)用户输入的表达式(如(3*x)*4/2)如果使用向基类添加方法的方法,实现起来可能会很麻烦)。
替代方法 - 标记联合/总和类型
有一个非常好的基本抽象被称为"tagged union"(它也被称为“不相交联合”和“总和类型”)。关于标记联合的主要思想是,您有几个不同实例集的联合,其中给定实例属于哪个集合。它们是 C++ 中称为enum 的功能的超集。遗憾的是,C++ 目前不支持标记联合,尽管有对其进行研究(例如 https://www.stroustrup.com/OpenPatternMatching.pdf ,但如果您是初学者程序员,这可能有点超出您的范围)。据我所知,这与您在此处给出的示例非常吻合。 Scala 中的一个示例是(许多其他语言也支持标记联合,例如 Rust、Kotlin、Typescript、ML 语言、Haskell 等):
sealed trait Item {
val name: String
}
case class Book(val name: String) extends Item
case object Fire extends Item {
val name = "Fire"
}
case class Container(val name: String, val inventory: List[Item]) extends Item
据我所知,这很好地描述了您的不同类型的项目。请注意,Scala 在这方面有点特殊,因为它通过子类型实现标记联合。
如果您想实现一些打印功能,您可以使用"pattern matching" 来匹配您拥有的项目并执行特定于该项目的功能。在支持模式匹配的语言中,这是方便且不易损坏的,因为模式匹配会检查您是否涵盖了每种可能的情况(类似于 C++ 中的switch 并通过枚举检查您是否涵盖了每种可能的情况)。例如在 Scala 中:
def getDescription(item: Item): String = {
item match {
case Book(_) | Fire => item.name
case Container(name, inventory) =>
name + " contains: (" +
inventory
.map(getDescription(_))
.mkString(", ") +
")"
}
}
val description = getDescription(
Container("Bag", List(Book("On Spelunking"), Fire))
)
println(description)
您可以在此处复制粘贴两个 sn-ps 并尝试运行它们:https://scalafiddle.io/。
这种建模非常适用于所谓的“数据类型”,类本身没有或只有很少的功能,类中的字段基本上是其接口的一部分(“接口”从某种意义上说,如果您添加、删除或更改类型的字段,您希望更改使用这些类型的实现)。
相反,当类内部的实现不是其接口的一部分时,我发现更传统的子类型建模和方法更方便,例如,如果我有一个描述碰撞系统接口的基类型,以及它的每个子类型具有不同的性能特点,适用于不同的情况。隐藏和保护实现,因为它不是接口的一部分,这很有意义,并且非常适合人们所说的“迷你模块”。
在 C++(和 C)中,尽管缺乏语言支持,有时人们还是会以各种方式使用标记联合。我看到在 C 中使用的一种方法是创建一个 C 联合(尽管要小心注册方面,例如内存和语义),其中使用 enum 标记来区分不同的情况。这很容易出错,因为您可能很容易在一个 enum 案例中访问一个字段,而该字段对于该 enum 案例无效。
您还可以将命令输入建模为标记联合。也就是说,解析可能会有些挑战,如果您是初学者,解析库可能会有点涉及;保持解析有点简单可能是个好主意。
旁注
C++ 是一种特殊的语言 - 我不太喜欢它,因为我不太关心资源使用或运行时性能等等,因为多种不同的原因,因为它可能很烦人并且开发起来不那么灵活。并且在其中进行开发可能具有挑战性,因为您必须始终小心避免undefined behaviour。也就是说,如果资源使用或运行时性能确实很重要,C++ 可以根据情况成为一个非常好的选择。 C++ 语言及其社区中还有许多非常有用和重要的见解,例如 RAII、所有权和生命周期。我的建议是学习 C++ 是一个好主意,但你也应该学习其他语言,例如静态类型的函数式编程语言。 FP(函数式编程)和支持 FP 的语言,有许多优点和缺点,但它们的一些优点非常非常好,尤其是 reg。不变性和副作用。
在这些语言中,Rust 可能在某些方面最接近 C++,尽管我没有使用 Rust 的经验,因此不能保证该语言或其社区。p>
作为旁注,您可能对此维基百科页面感兴趣:https://en.wikipedia.org/wiki/Expression_problem。