英文:
Extend calculator with complex and rational module(using dynamic binding)
问题
我已经制作了一个可以计算整数和实数的计算器(我用Go语言制作的)。
然后我想通过添加这些模块来使其能够计算复数和有理数(它也可以在类型混合的情况下进行计算)。
如果我每次运行时都检查操作数的类型并处理每种情况,那么这可能很容易,但我想用动态绑定来解决这个问题。你们能告诉我如何解决这个问题的思路吗?
英文:
I already made calculator that can compute with integers and real numbers(i made it with go).
Then I want to make it possible to calculate complex and rational numbers by adding those modules.
(it can also calculate when types are mixed)
It can be easy if I check types of operands every time(runtime) and take care of each case, but I want to solve it with dynamic binding. Guys can you tell me the idea of how to solve this problem
答案1
得分: 0
我认为你提到的动态类型,可能是指在C++和Java等语言中,动态绑定实际上是指具有指向派生类的基类引用(因为派生类“是一个”基类)。
可以说基类定义了派生类变形背后的接口。如果派生类替换了基类的方法,那么对基类的引用仍然可以访问派生类中的这些方法。
基类可以定义一些方法,为其派生类提供一些“基本”功能。如果这些类没有重新定义该方法,就可以调用基类的方法。
在Go语言中,这两个概念也存在,但它们是完全不同的。
Go语言有一个显式的interface
关键字,用于定义方法签名但不包含方法体。只要具有相同名称和签名的方法,任何值都会隐式满足该接口。
type LivingBeing interface {
TakeInEnergy()
ExpelWaste()
}
接口类型在代码中成为有效的类型。我们可以将接口传递给函数,并且在不知道满足该接口的类型的情况下,可以调用其方法:
func DoLife(being LivingBeing) {
being.TakeInEnergy()
being.ExpelWaste()
}
这是有效的代码,但不是完整的代码。与其他语言中的基类不同,接口不能定义函数,只能定义函数签名。它纯粹只是一个接口定义。我们必须将满足接口的类型与接口本身分开定义。
type Organism struct{}
func (o *Organism) TakeInEnergy() {}
func (o *Organism) ExpelWaste() {}
现在我们有一个满足LivingBeing
接口的Organism
类型。它有点像一个基类,但如果我们想要构建它,我们不能使用子类化,因为Go语言不支持。但是Go提供了类似的东西,称为嵌入类型。
在这个例子中,我将定义一个新的生物体Animal
,它从LivingBeing
中继承了ExpelWaste()
,但定义了自己的TakeInEnergy()
:
type Animal struct {
Organism // 嵌入类型的语法:类型但没有字段名
}
func (a *Animal) TakeInEnergy() {
fmt.Printf("我是一个动物")
}
从那段代码中并不明显的是,因为Animal
的Organism
不是一个命名字段,所以它的字段和方法可以直接从Animal
访问。就好像Animal
“是一个”生物体一样。
然而,它不是一个生物体。它是一个不同的类型,更准确地说,将对象嵌入视为将Organism
的字段和方法自动提升到Animal
的语法糖。
由于Go是静态和显式类型的,DoLife
不能接受一个Organism
,然后传递一个Animal
:它们的类型不同:
/* 这是不起作用的。Animal嵌入organism,但*不是*organism */
func DoLife(being *Organism) {
being.TakeInEnergy()
being.ExpelWaste()
}
func main() {
var a = &Animal{Organism{}}
DoLife(&Animal{})
}
无法将&Animal{}(类型*Animal)作为参数传递给DoLife
这就是为什么存在interface
的原因-这样Organism
和Animal
(甚至Plant
或ChemotrophBacteria
)都可以作为LivingBeing
传递。
将所有内容放在一起,这是我一直在使用的代码:
package main
import "fmt"
type LivingBeing interface {
TakeInEnergy()
ExpelWaste()
}
type Organism struct{}
func (o *Organism) TakeInEnergy() {
}
func (o *Organism) ExpelWaste() {}
type Animal struct {
Organism
}
func DoLife(being LivingBeing) {
being.TakeInEnergy()
being.ExpelWaste()
}
func (a *Animal) TakeInEnergy() {
fmt.Printf("我是一个动物")
}
func main() {
var a = &Animal{Organism{}}
DoLife(a)
}
有几个注意事项:
-
语法上,如果要声明一个嵌入的字面量,必须显式提供其类型。在我的例子中,
Organism
没有字段,所以没有什么需要声明的,但我仍然将显式类型留在那里,以指导你的方向。 -
DoLife
可以在LivingBeing
上调用正确的TakeInEnergy
,但Organism
的方法不能。它们只能看到嵌入的Organism
。
func (o *Organism) ExpelWaste() {
o.getWaste() // 这将始终是Organism的getWaste,而不是Animal的
}
func (o *Organism)getWaste() {}
func (a *Animal)getWaste() {
fmt.Println("动物的废物")
}
类似地,如果只传递嵌入的部分,那么它将调用自己的TakeInEnergy()
,而不是Animal
的;没有Animal
了!
func main() {
var a = &Animal{Organism{}}
DoLife(&a.Organism)
}
总结一下,
-
显式定义
interface
,并在需要“多态”行为的地方使用该类型 -
定义基本类型并将其嵌入到其他类型中以共享基本功能。
-
不要期望“基类”能够“绑定”到“派生类”的函数。
英文:
I think by dynamic typing, you're probably referring to how in eg C++ and Java, dynamic binding is essentially having reference to a base class that can point to a derived class (because the derived class "is a" base class).
One might say that the base class defines the interface behind which derived classes morph. If the derived classes replace methods of the base class, a reference to the base class can still access those methods in the derived class.
Base class can define some methods to provide some "base" functionality to its derived classes. If those classes don't redefine the method, the base class's method can be called.
In go, both these concepts exist, but they're quite different.
Go has an explicit interface
keyword that defines method signatures but no methods. Any value implicitly satisfies that interface if it has methods of the same name with the same signature.
type LivingBeing interface {
TakeInEnergy()
ExpelWaste()
}
The interface type becomes a valid type in the code. We can pass an interface to a function, and without knowing the type satisfying that interface, can call its methods:
func DoLife(being LivingBeing) {
being.TakeInEnergy()
being.ExpelWaste()
}
This is valid code, but not complete code. Unlike with base classes in other languages, interfaces cannot define functions, only their signatures. It is purely and only an interface definition. We have to define the types that satisfy the interface separately from the interface itself.
type Organism struct{}
func (o *Organism) TakeInEnergy() {}
func (o *Organism) ExpelWaste() {}
We now have a type organism
that satisfies LivingBeing
. It is something like a base class, but if we wanted to build on it, we can't use subclassing because Go doesn't implement it. But Go does provide something similar called embedding types.
In this example I'll define a new organism, Animal, that draws ExpelWaste()
from LivingBeing
, but defines its own TakeInEnergy()
:
type Animal struct {
Organism // the syntax for an embedded type: type but no field name
}
func (a *Animal) TakeInEnergy() {
fmt.Printf("I am an animal")
}
What isn't obvious from that code is that because Animal
's Organism
is not a named field, its fields and methods are accessible directly from Animal
. It's almost as if Animal
"is an" organism.
However it is *not * an organism. It is a different type, and it would be more accurate to think of object embedding as syntactic sugar to automatically promote Organism
's fields and methods to Animal
.
Since go is statically and explicitly typed, DoLife
cannot take an Organism
and then be passed an Animal
: it doesn't have the same type:
/* This does not work. Animal embeds organism, but *is not* an organism */
func DoLife(being *Organism) {
being.TakeInEnergy()
being.ExpelWaste()
}
func main() {
var a = &Animal{Organism{}}
DoLife(&Animal{})
}
cannot use &Animal{} (type *Animal) as type *Organism in argument to DoLife
That's why interface
exists - so that both Organism
and Animal
(and indeed, even Plant
or ChemotrophBacteria
) can be passed as a LivingBeing
.
Putting it all together, here's the code I've been working from:
package main
import "fmt"
type LivingBeing interface {
TakeInEnergy()
ExpelWaste()
}
type Organism struct{}
func (o *Organism) TakeInEnergy() {
}
func (o *Organism) ExpelWaste() {}
type Animal struct {
Organism
}
func DoLife(being LivingBeing) {
being.TakeInEnergy()
being.ExpelWaste()
}
func (a *Animal) TakeInEnergy() {
fmt.Printf("I am an animal")
}
func main() {
var a = &Animal{Organism{}}
DoLife(a)
}
There are a few caveats:
-
Syntactically, If you want to declare an embedded literal you must explicitly provide its type. In my example
Organism
has no fields so there's nothing to declare, but I still left the explicit type in there to point you in the right direction. -
DoLife
can call the rightTakeInEnergy
on a LivingBeing, butOrganism
's methods cannot. They see only the embeddedOrganism
.
func (o *Organism) ExpelWaste() {
o.getWaste() //this will always be Organism's getWaste, never Animal's
}
func (o *Organism)getWaste() {}
func (a *Animal)getWaste() {
fmt.Println("Animal waste")
}
Simlarly if you pass only the embedded part, then it's going to call its own TakeInEnergy()
, not that of Animal
; there's no Animal
left!
func main() {
var a = &Animal{Organism{}}
DoLife(&a.Organism)
}
In summary,
-
Define explict
interface
and use that type wherever you want "polymorphic" behavior -
Define base types and embed them in other types to share base functionality.
-
Don't expect the "base" type to ever "bind" to functions from the "derived" type.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论