Skip to content

Commit

Permalink
Grouping and Decoupling typo fixed (hoanhan101#23)
Browse files Browse the repository at this point in the history
* method, interface typo fixed

* embedding & exporting typo fixed

* Grouping Types Typo fixed

* decoupling typo fixed

* concrete can be noun
  • Loading branch information
Thammachart authored and Hoanh An committed Nov 12, 2019
1 parent 286bf90 commit d5c89b1
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 51 deletions.
37 changes: 19 additions & 18 deletions go/design/decoupling_1.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// concrete first. Then we can ask ourselves: What can change? What change is coming? so we can
// start decoupling and refactor.

// Refactoring has to become a part of the development cycle.
// Refactoring need to become a part of the development cycle.

// Here is the problem that we are trying to solve in this section.
// We have a system called Xenia that has a database.
Expand All @@ -16,24 +16,25 @@

// How long is it gonna take?
// How do we know when a piece of code is done so we can move on the next piece of code?
// If you are a technical manager, how do you know your debt is wasting effort or not putting
// enough effort?
// Done has 2 parts:
// If you are a technical manager,
// how do you know whether your debt is "wasting effort" or "not putting enough effort"?

// Being done has 2 parts:
// One is test coverage, 80% in general and 100% on the happy path.
// Second is about changes. By asking what can changes, from technical perspective and business
// Second is about changes. By asking what can change, from technical perspective and business
// perspective, we make sure that we refactor the code to be able to handle that change.

// One example is, we can give you a concrete version in 2 days but we need 2 weeks to be able to
// refactor this code to deal with the change that we know it's coming.

// The plan is to solve one problem at a time. Don't be overwhelm by everything.
// Write a little code, write some tests refactor. Write layer of APIs that work on top of each
// The plan is to solve one problem at a time. Don't be overwhelmed by everything.
// Write a little code, write some tests and refactor. Write layer of APIs that work on top of each
// other, knowing that each layer is a strong foundation to the next.

// Let's not too hung out on the implementation details. It's the mechanics here that are
// important.
// We are optimizing for correctness, not performance. We can always go back if it doesn't perform
// well enough to speed thing up.
// Do not pay too much attention in the implementation detail.
// It's the mechanics here that are important.
// We are optimizing for correctness, not performance.
// We can always go back if it doesn't perform well enough to speed things up.

// Next step:
// ----------
Expand All @@ -56,7 +57,7 @@ func init() {
}

// Data is the structure of the data we are copying.
// For simplicity, just pretend that is is a string data.
// For simplicity, just pretend it is a string data.
type Data struct {
Line string
}
Expand All @@ -68,8 +69,8 @@ type Xenia struct {
}

// Pull knows how to pull data out of Xenia.
// We could do func (*Xenia) Pull (*Data, error) that return the data and error. However, this
// would cost an allocation on every call we don't want that.
// We could do func (*Xenia) Pull() (*Data, error) that return the data and error. However, this
// would cost an allocation on every call and we don't want that.
// Using the function below, we know data is a struct type and its size ahead of time. Therefore
// they could be on the stack.
func (*Xenia) Pull(d *Data) error {
Expand Down Expand Up @@ -111,11 +112,11 @@ type System struct {
Pillar
}

// pull knows how to pull bulks of data from Xenia, leveraging the foundation that we built.
// pull knows how to pull bulks of data from Xenia, leveraging the foundation that we have built.
// We don't need to add method to System to do this. There is no state inside System that we want
// the system to maintain. Instead, we want the System to understand the behavior.
// the System to maintain. Instead, we want the System to understand the behavior.
// Functions are a great way of writing API because functions can be more readable than any method
// can. We always want to start with an idea of writing API from the package level with function.
// can. We always want to start with an idea of writing API from the package level with functions.
// When we write a function, all the input must be passed in. When we use a method, its signature
// doesn't indicate any level, what field or state that we are using on that value that we use to
// make the call.
Expand Down Expand Up @@ -146,7 +147,7 @@ func store(p *Pillar, data []Data) (int, error) {
}

// Copy knows how to pull and store data from the System.
// Now we can call the pull and store function, passing Xenia and Pillar through.
// Now we can call the pull and store functions, passing Xenia and Pillar through.
func Copy(sys *System, batch int) error {
data := make([]Data, batch)

Expand Down
4 changes: 2 additions & 2 deletions go/design/decoupling_2.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Decoupling With Interface
// -------------------------

// By looking at the API (function), we need to decouple the API from the concrete. The decoupling
// By looking at the API (functions), we need to decouple the API from the concrete implementation. The decoupling
// that we do must get all the way down into initialization. To do this right, the only piece of
// code that we need to change is initialization. Everything else should be able to act on the
// behavior that these types are gonna provide.
Expand All @@ -14,7 +14,7 @@

// It is nice to work from the concrete up. When we do this, not only we are solving problem
// efficiently and reducing technical debt but the contracts, they come to us. We already know what
// the contract is for pulling/storing data. We already validate that this is what we need.
// the contract is for pulling/storing data. We already validate that and this is what we need.

// Let's just decouple these 2 functions and add 2 interfaces. The Puller interface knows how to
// pull and the Storer knows how to store.
Expand Down
21 changes: 10 additions & 11 deletions go/design/decoupling_3.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@

// Let's just add another interface. Let's use interface composition to do this.
// PullStorer has both behaviors: Puller and Storer. Any concrete type that implement both pull and
// store is a PullStorer. System is a PullStorer because it is embedded of these 2 types Xenia and
// store is a PullStorer. System is a PullStorer because it is embedded of these 2 types, Xenia and
// Pillar. Now we just need to go into Copy, replace the system pointer with PullStorer and no
// other code need to change. When we call Copy passing the address of our System value in main,
// that already implement the PullStorer interface.
// other code need to change.

// Looking closely at Copy, there is something that could potentially confuse us. We are passing
// the PullStorer interface value into pull and store respectively.
// the PullStorer interface value directly into pull and store respectively.
// If we look into pull and store, they don't want a PullStorer. One want a Puller and one want a
// Storer. Why does the compiler allow us to pass a value of different type value while it didn't
// allow us to do that before?
Expand All @@ -21,15 +20,15 @@
// behaviors for another interface. It is true that any concrete type that is stored inside of a
// PullStorer must also implement the Storer and Puller.

// Let's walkthrough the code.
// In the main function, we are creating a value of our type System. As we know, our type System
// Let's further look into the code.
// In the main function, we are creating a value of our System type. As we know, our System type
// value is based on the embedding of two concrete types: Xenia and Pillar, where Xenia knows how
// to pull and Pillar knows how to store. Because of inner promotion, System knows also how to pull
// and store.
// to pull and Pillar knows how to store.
// Because of inner type promotion, System knows how to pull and store both inherently.
// We are passing the address of our System to Copy. Copy then creates the PullStorer interface.
// The first word is a System pointer and the second word point to the original value. This
// interface now knows how to pull and store. When we call pull off of ps, we call pull off of
// System, which call pull off of Xenia.
// System, which eventually call pull off of Xenia.
// Here is the kicker: the implicit interface conversion.
// We can pass the interface value ps to pull because the compiler knows that any concrete type
// stored inside the PullStorer must also implement Puller. We end up with another interface called
Expand Down Expand Up @@ -62,7 +61,7 @@
// ----------
// Our system type is still concrete system type because it is still based on two concrete types,
// Xenial and Pillar. If we have another system, say Alice, we have to change in type System
// struct. This it not good. We will solve the last piece in the next file.
// struct. This is not good. We will solve the last piece in the next file.

package main

Expand Down Expand Up @@ -93,7 +92,7 @@ type Storer interface {
Store(d *Data) error
}

// PullStorer declares behavior for both pulling and storing.
// PullStorer declares behaviors for both pulling and storing.
type PullStorer interface {
Puller
Storer
Expand Down
4 changes: 2 additions & 2 deletions go/design/decoupling_4.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// behaviors is now based on the embedding of 2 interface types. It means that we can inject any
// data, not based on the common DNA but on the data that providing the capability, the behavior
// that we need.
// Now we can be fully decouple because any value that implements the Puller interface can be store
// Now our code can be fully decouplable because any value that implements the Puller interface can be stored
// inside the System (same with Storer interface). We can create multiple Systems and that data can
// be passed in Copy.
// We don't need method here. We just need one function that accept data and its behavior will
Expand Down Expand Up @@ -78,7 +78,7 @@ type Storer interface {
Store(d *Data) error
}

// PullStorer declares behavior for both pulling and storing.
// PullStorer declares behaviors for both pulling and storing.
type PullStorer interface {
Puller
Storer
Expand Down
20 changes: 10 additions & 10 deletions go/design/grouping_types_1.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Grouping By State
// -----------------

// This is an example of using type hierarchies with a OOP pattern.
// This is an example of using type hierarchies with an OOP pattern.
// This is not something we want to do in Go. Go does not have the concept of sub-typing.
// All types are their own and the concepts of base and derived types do not exist in Go.
// This pattern does not provide a good design principle in a Go program.
Expand All @@ -11,7 +11,7 @@ package main

import "fmt"

// Animal contains all the base fields for animals.
// Animal contains all the base attributes for animals.
type Animal struct {
Name string
IsMammal bool
Expand All @@ -27,7 +27,7 @@ func (a *Animal) Speak() {
"I am a mammal")
}

// Dog contains everything an Animal is but specific attributes that only a Dog has.
// Dog contains everything from Animal, plus specific attributes that only a Dog has.
type Dog struct {
Animal
PackFactor int
Expand All @@ -41,7 +41,7 @@ func (d *Dog) Speak() {
"I am a mammal with a pack factor of", d.PackFactor)
}

// Cat contains everything an Animal is but specific attributes that only a Cat has.
// Cat contains everything from Animal, plus specific attributes that only a Cat has.
type Cat struct {
Animal
ClimbFactor int
Expand All @@ -58,11 +58,11 @@ func (c *Cat) Speak() {
func main() {
// It's all fine until this one. This code will not compile.
// Here, we try to group the Cat and Dog based on the fact that they are Animals. We are trying
// to leverage sub typing in Go. However, Go doesn't have it.
// Go doesn't say let group thing by a common DNA.
// to leverage sub-typing in Go. However, Go doesn't have it.
// Go doesn't encourage us to group types by common DNA.
// We need to stop designing APIs around this idea that types have a common DNA because if we
// only focus on who we are, it is very limiting on who can we group with.
// Sub typing doesn't promote diversity. We lock type in a very small subset that can be
// Sub-typing doesn't promote diversity. We lock types in a very small subset that can be
// grouped with. But when we focus on behavior, we open up entire world to us.
animals := []Animal{
// Create a Dog by initializing its Animal parts and then its specific Dog attributes.
Expand Down Expand Up @@ -95,7 +95,7 @@ func main() {
// ----------

// This code smells bad because:
// - The Animal type is providing an abstraction layer of reusable state.
// - The program never needs to create or solely use a value of type Animal.
// - The implementation of the Speak method for the Animal type is a generalization.
// - The Animal type provides an abstraction layer of reusable state.
// - The program never needs to create or solely use a value of Animal type.
// - The implementation of the Speak method for the Animal type is generalization.
// - The Speak method for the Animal type is never going to be called.
16 changes: 8 additions & 8 deletions go/design/grouping_types_2.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
// This pattern does provide a good design principle in a Go program.

// We will group common types by their behavior and not by their state.
// What brilliant about Go is that it doesn't have to be configured ahead of time. The compiler
// identifies interface and behaviours at compiled time. In means that we can write code today that
// What brilliant about Go is that it doesn't have to be configured ahead of time. The compiler automatically
// identifies interface and behaviors at compile time. It means that we can write code today that
// compliant with any interface that exists today or tomorrow. It doesn't matter where that is
// declared because the compiler can do this on the fly.

Expand Down Expand Up @@ -62,22 +62,22 @@ func (c Cat) Speak() {
func main() {
// Create a list of Animals that know how to speak.
speakers := []Speaker{
// Create a Dog by initializing its Animal parts and then its specific Dog attributes.
// Create a Dog by initializing Dog attributes.
Dog{
Name: "Fido",
IsMammal: true,
PackFactor: 5,
},

// Create a Cat by initializing its Animal parts and then its specific Cat attributes.
// Create a Cat by initializing Cat attributes.
Cat{
Name: "Milo",
IsMammal: true,
ClimbFactor: 4,
},
}

// Have the Animals speak.
// Have the Speakers speak.
for _, spkr := range speakers {
spkr.Speak()
}
Expand All @@ -89,7 +89,7 @@ func main() {

// - Declare types that represent something new or unique. We don't want to create aliases just for readability.
// - Validate that a value of any type is created or used on its own.
// - Embed types not because we need the state but because we need the behavior. If we not thinking
// about behavior, we are really locking ourselves into the design that we cannot grow in the future.
// - Question types that are an alias or abstraction for an existing type.
// - Embed types not because we need the state but because we need the behavior. If we are not thinking
// about behavior, we are locking ourselves into the design that we cannot grow in the future.
// - Question types that are aliases or abstraction for an existing type.
// - Question types whose sole purpose is to share common state.

0 comments on commit d5c89b1

Please sign in to comment.