Golang初级系列教程-接口2
在上一篇文章中,已经通过一个典型的 OOP
的例子讲述了 Go
中接口的概念。Bob 在Google论坛上指出:Go
接口比 Java
或者 C#
的更加灵活多变,更加适用于大型编程,是一项跨时代的设计。(“Go’s interfaces aren’t a variant on Java or C# interfaces, they’re much more. They are a key to large-scale programming and adaptable, evolutionary design.” )
首先,通过 Java
接口对比,从而揭示这种革新的变化。让我们通过 Bus
来讲述,一辆 Bus
可以认为实现两个接口——框架可以用来计算体积和交通工具用来载客。
Java
代码
//OOP Step 1: design your interface and class hierarchy
//OOP Step 1.1: Pre-define what real-world abstractions could use the data we will define
interface PublicTransport {
int PassengerCapacity();
}
//OOP Step - repeat for any other interfaces
interface Cuboid {
int CubicVolume();
}
//OOP Step 2: Create data structures and implement all interfaces we have already defined in our class hierarchy
public class Bus implements
PublicTransport,
Cuboid {
//OOP Step 2.1: Define data structures for class
int l, b, h, rows, seatsPerRow;
public Bus(int l, int b, int h, int rows, int seatsPerRow) {
this.l = l; this.b = b; this.h = h; this.rows = rows; this.seatsPerRow = seatsPerRow;
}
//OOP Step 2.2: Define method implementation
public int CubicVolume() { return l*b*h; }
public int PassengerCapacity() { return rows * seatsPerRow; }
//OOP Step 3: Use the classes and methods in main program
public static void main() {
Bus b = new Bus(10, 6, 3, 10, 5);
System.out.Println(b.CubicVolume());
System.out.Println(b.PassengerCapacity());
}
}
注意:上面 Java
代码中,必须通过 implements
关键字,描述类之间的关系,在 C#
中则需要使用 :
代替 implements
。这种情况下,如果类之间的关系更新了,不得不修改这些核心的模块。
大多数项目,都有一个长期的架构和设计时期,在这期间需要不断的更新接口和数据结构,直到所有的因素都考虑到,然而这就需要不断的更新核心代码。一旦设计结构最终定好之后,如果没有太大的改动理由,是不会去更改类之间的层次结构的。在我工作过的项目中,架构是由专业的架构师负责,如果不是评审委员会同意架构是不会产生变化的。上游模块的改动往往导致下游模块也跟着一起改动。另一种方案,是重新封装现有的类,生成新的接口,但这往往又会造成系统庞大,难以管理。为了解决复杂性和模块的变化,设计模式应运而生。我承认,我非常喜欢设计模式和面向对象的编程过程。但是,设计模式很多,需要我们必须有很好的专业知识,也需要一个很长的学习过程。与其花费更多的设计时间,Go
可否能提供更直观简单的方式呢?
接下来,让我们把上面的代码用 Go
实现。
package main
import "fmt"
//Go Step 1: Define your data structures
type Bus struct {
l, b, h int
rows, seatsPerRow int
}
//Go Step 2: Define a real world abstraction that could use the data we structure we have
type Cuboider interface {
CubicVolume() int
}
//Go Step 3: Implement methods to work on data
func (bus Bus) CubicVolume() int {
return bus.l * bus.b * bus.h
}
//Go step - repeat 2 & 3 for any other interfaces
type PublicTransporter interface {
PassengerCapacity() int
}
func (bus Bus) PassengerCapacity() int {
return bus.rows * bus.seatsPerRow
}
func main() {
b := Bus{
l:10, b:6, h:3,
rows:10, seatsPerRow:5}
fmt.Println("Cubic volume of bus:", b.CubicVolume())
fmt.Println("Maximum number of passengers:", b.PassengerCapacity())
}
类型变量和结构名完全相同,但是仍有些许区别。其中之一,便是不需要像 Java
中的关键字 implements
去声明类之间层次结构。另外,Go
以数据为中心——首先定义数据结构,之后根据需要定义接口。层次结构在 Go
中表现为一种共识,实现相同的方法即可认为是属于同一种接口定义。在上面的例子中,由于实现的功能相同,两种设计的差异不突出,但是随着程序不断的添加新功能,Go
的特性就会显现出来。
假设当前需求变化,要求每一辆 Bus
必须保证每名乘客都至少有一定的空间。 Bus
必须实现另一个接口 PersonalSpaceLaw
,在 Java
或者其它语言中,我们可能进行如下的代码设计:
//new requirement that the Bus must be compatible with
interface PersonalSpaceLaw {
boolean IsCompliantWithLaw();
}
class Bus implements
PublicTransport,
Cuboid,
PersonalSpaceLaw {
//... other existing code
public IsCompliantWithLaw()
{
return ( l * b * h ) / ( rows * seatsPerRow ) > 3;
}
}
上述代码中,改变了类之间的层次关系,导致我们不得不修改核心代码,然而这就产生了问题。如果核心代码已经很长时间没有变更或者已经成型,为了添加这个功能我们不得不说服委员会去更新设计结构,所以我们需要准备洽谈、文档、开会等等等。
让我们看看 Go
能做什么吧
//new requirement that the Bus must be compatible with
type PersonalSpaceLaw interface {
IsCompliantWithLaw() bool
}
func (b Bus) IsCompliantWithLaw() bool {
return (b.l * b.b * b.h) / (b.rows * b.seatsPerRow) >= 3
}
看到没,之前的代码一点没改,就完成了结构的扩展。是不是更加干净、灵活,对新需求更加容易扩展。
引用 John Asmuth 发表在论坛上的话作为总结:不必提前花费时间设计并多次修整类结构;根本无需关心这些,随着编码的进展,算法结构已经符合实际情况。
注意:在这篇文章中,并没有贬低或者不重视优秀、简洁的设计,毕竟时间在前进,未来的需求不可能完全考虑到。
完整代码
package main
import "fmt"
type Bus struct {
l, b, h int
rows, seatsPerRow int
}
type Cuboider interface {
CubicVolume() int
}
func (b Bus) CubicVolume() int {
return b.l * b.b * b.h
}
type PublicTransporter interface {
PassengerCapacity() int
}
func (b Bus) PassengerCapacity() int {
return b.rows * b.seatsPerRow
}
func main() {
b := Bus{
l:10, b:6, h:3,
rows:10, seatsPerRow:5}
fmt.Println("Cubic volume of b:", b.CubicVolume())
fmt.Println("Maximum number of passengers:", b.PassengerCapacity())
fmt.Println("Is compliant with law:", b.IsCompliantWithLaw())
}
type PersonalSpaceLaw interface {
IsCompliantWithLaw() bool
}
func (b Bus) IsCompliantWithLaw() bool {
return (b.l * b.b * b.h) / (b.rows * b.seatsPerRow) >= 3
}
Golang一种神奇的语言,让我们一起进步