type Fruit interface {
String()
string
}
type Apple struct {
Variety string
}
func (a Apple) String() string {
return fmt.Sprintf("A %s apple", a.Variety)
}
type Orange struct {
Size string
}
func (o Orange) String() string {
return fmt.Sprintf("A %s orange", o.Size)
}
func PrintFruit(fruit Fruit) {
fmt.Println("I have this fruit:", fruit.String())
}
func main() {
apple := Apple{"Golden Delicious"}
orange := Orange{"large"}
PrintFruit(apple)
PrintFruit(orange)
}
I have this fruit:
A Golden Delicious apple
I have this fruit: A large orange
Here we’ve defined an interface type
Fruit
, which requires a single method to be
implemented:
String() string
. This method takes no parameters and returns a
string, so any type with a
String
method that returns a string will be considered to
implement
Fruit
. After that we’ve created two new struct types,
Apple
and
Orange
,
both of which have different fields but implement the
String
method correctly.
These types both implement
Fruit
. Lastly, we’ve created a function that takes a
Fruit
instance and prints out the result of the
String
method. In our main function
Level
Up Your Web Apps With Go
26
we create an
orange
and an
apple
, then pass them to the
PrintFruit
function. It’s
important to note that the variables
apple
and
orange
have types of
Apple
and
Or-
ange
respectively. Only when they’re passed into the function
PrintFruit
is their
type
Fruit
.
The power of interfaces is in that the calling code is unaffected by what the under-
lying type or implementation, as long as it match the method signatures. This is
powerful when writing packages that are designed for reuse.
Let’s look at a new hypothetical situation: Say you sell a web application that offers
a product catalog. At first your customers only have a few products to sell, so you
just store the information in a file and load it when the application runs. Then you
have a customer with a large amount of products, so it’s time to move the products
into a database of some sort. If you want to use the same codebase to handle the
products but abstract away the details of where the products are stored and retrieved
from, you could define an interface for the product catalogue and then have different
implementations for the file catalog and database catalog. We’ll skip going into the
implementations, but we can look at the general structure of how this might work.
First we’d create an interface to define what methods we’re dealing with for a product
catalog:
type ProductCatalogue interface {
All() ([]Product, error)
Find(string) (Product, error)
}
This sets us up to handle getting all of the products and getting just a specific
product
―
the general requirement for a small ecommerce website. There might be
more methods required, but I’ll keep this example minimal. Next up we'll define
the actual implementations of the interface—for the file storage and for some database
storage:
type FileProductCatalogue struct {
… //
Some fields would be here,
perhaps the file location
}
func (c FileProductCatalogue) All() ([]Product, error) {
… //
Implementation omitted
}
27
Go Types Explored
func (c FileProductCatalogue) Find(id string) (Product, error) {
… // Implementation omitted
}
type DBProductCatalogue struct {
… // Some fields would be here, perhaps a database connection
}
func (c DBProductCatalogue) All() ([]Product, error) {
… // Implementation omitted
}
func (c DBProductCatalogue) Find(id string) (Product, error) {
… // Implementation omitted
}
Now we have two very different implementations of the same interface. One that
loads from a file and one that loads from a database. When we’re creating a variable,
we can do so with a type of
ProductCatalogue
rather than the specific implement-
ation:
func main() {
var myCatalogue ProductCatalogue
myCatalogue = DBProductCatalogue{}
…
}
Or we could have an initialization function that accepts any
ProductCatalogue
implementation:
func LoadProducts(catalogue ProductCatalogue) {
products, err := catalogue.All()
…
}
Do'stlaringiz bilan baham: