This is a creation in Article, where the information may have evolved or changed.
Preface
Two months ago, the author was fortunate to participate in a code retreat activities, mainly responsible for the review of Golang group of codes, the whole process is very rewarding, especially to the end of the event, the heavyweight Liu Guangzong share his C + + version and the Scala version of the works, so that the people call the fun, So there is an idea to implement the Golang version of the work.
It happened that the author was starting to write a series of articles related to Golang unit testing, so the matter was temporarily stranded until the four articles that had been planned were completed. After all, two months later, Code retreat activities in the needs of some of the details of the evolution of some forgotten, so look at Jane Book, found the relevant article, that is, Liu Guangzong classmate's classic article "Application of orthogonal design and modular design", seriously learn to feel very valuable, immediately opened the Golang version of the implementation of the journey.
Requirement One: Determine whether a word contains numbers
This requirement is relatively simple and the code is implemented as follows:
func HasDigit(word string) bool { for _, c := range word { if unicode.IsDigit(c) { return true } } return false}
Requirement Two: Determine if a word contains uppercase letters
With the foundation of demand one, we can quickly realize demand by copy-paste two:
func HasDigit(word string) bool { for _, c := range word { if unicode.IsDigit(c) { return true } } return false}func HasUpper(word string) bool { for _, c := range str { if unicode.IsUpper(c) { return true } } return false}
Obviously, the Hasdigit function and the Hasupper function are all the same except for the IF condition, so we use abstract this powerful dragon Slayer to eliminate repetition:
- Defines an interface Charspec, as an abstraction of all character predicates, method satisfy to determine whether a predicate is true
- Defines a predicate with atomic semantics for a requirement isdigit
- Defines a predicate with atomic semantics for requirement two isupper
The predicate-related code is implemented as follows:
type CharSpec interface { Satisfy(c rune) bool}type IsDigit struct {}func (i IsDigit) Satisfy(c rune) bool { return unicode.IsDigit(c)}type IsUpper struct {}func (i IsUpper) Satisfy(c rune) bool { return unicode.IsUpper(c)}
To complete the requirement, the predicate must also be injected into the has semantic function of the word, while the exists has the has semantics, while the expression force is strong:
func Exists(word string, spec CharSpec) bool { for _, c := range word { if spec.Satisfy(c) { return true } } return false}
Determine whether a word word contains numbers by exists:
isDigit := IsDigit{}ok := Exists(word, isDigit)...
Determine whether a word word contains uppercase letters by exists:
isUpper := IsUpper{}ok := Exists(word, isUpper)...
In fact, the story of demand two is not finished:)
For the average programmer, it is good to complete the above code, and for experienced programmers, after the requirements have been completed, the new direction of change, that is, the word has semantics and the character is semantics is two direction of change, so before the need to start the reconstruction of the separation of the direction of change:
type IsDigit struct {}func (i IsDigit) Satisfy(c rune) bool { return unicode.IsDigit(c)}func Exists(word string, spec IsDigit) bool { for _, c := range word { if spec.Satisfy(c) { return true } } return false}
Determine whether a word word contains numbers by exists:
isDigit := IsDigit{}ok := Exists(word, isDigit)...
After the demand two came out, the predicate was hit by the first bullet, we applied the open closure principle according to Uncle Bob's suggestion, and then wrote out the code that the ordinary programmer eliminated the repetition after the requirement two o'clock.
This is not a coincidence, but a theoretical basis.
Let's take a look at the orthogonal design four principles proposed by Mr. Shingjie :
- One change leads to multiple modifications: de-duplication
- Multiple changes result in one modification: separating different directions of change
- Do not rely on unnecessary dependencies: narrowing down the range of dependencies
- Not dependent on unstable dependence: towards a stable direction
The four principles are proposed to make the implementation of the second "de-duplication" goal of the simple design four principle more explicit practice guidance. Using the orthogonal design four principle, we can decompose the system into small classes with many single functions, and then combine them flexibly as needed.
Savor the orthogonal design four principles, you will find: The first one is a passive strategy, and then three is the active strategy. This means that the first article is a strategy for post-mortem remediation, and the latter three is a strategy for proactive prevention that aims to eliminate duplication.
As you can see from the above analysis, ordinary programmers are better at using passive strategies, and experienced programmers are better at using proactive strategies. Anyway, they are the same, have eliminated the repetition.
Requirement Three: Determine if a word contains _
Whether it is an underscore or an underscore, there is atomic semantics equals, and we quickly implement the code:
type Equals struct { c rune}func (e Equals) Satisfy(c rune) bool { return c == e.c}
Use exists to determine whether Word contains _ in a word:
isUnderline := Equals{'_'}ok := Exists(word, isUnderline)...
Requirement Four: Determine if a word does not contain _
The letter is an underscore predicate is equals, then the letter is not an underscore predicate is to add a modifier to equals before the Isnot,isnot modifier verb is a new predicate, the code is implemented as follows:
type IsNot struct { spec CharSpec}func (i IsNot) Satisfy(c rune) bool { return !i.spec.Satisfy(c)}
Words do not contain underscores, it is not exists semantics, but forall semantics, the code is implemented as follows:
func ForAll(word string, spec CharSpec) bool { for _, c := range str { if !spec.Satisfy(c) { return false } } return true}
Use ForAll to determine if Word does not contain _:
isNotUnderline := IsNot{Equals{'_'}}ok := ForAll(word, isNotUnderline)...
Functionality has been implemented, but we have found that exists functions and forall functions have a lot of code that repeats, using the refactoring basic technique of extract method:
func expect(word string, spec CharSpec, ok bool) bool { for _, c := range word { if spec.Satisfy(c) == ok { return ok } } return !ok}func Exists(word string, spec CharSpec) bool { return expect(word, spec, true)}func ForAll(word string, spec CharSpec) bool { return expect(word, spec, false)}
Requirement Five: Determine if a word contains _ or *
The predicate with the letter X or Y has a combined semantic anyof, where x is equals{' _ '},y is equals{' * '}, the code is implemented as follows:
type AnyOf struct { specs []CharSpec}func (a AnyOf) Satisfy(c rune) bool { for _, spec := range a.specs { if spec.Satisfy(c) { return true } } return false}
Use exists to determine whether Word contains _ or * in a word:
isUnderlineOrStar := AnyOf{[]CharSpec{Equals{'_'}, Equals{'*'}}}ok := Exists(word, isUnderlineOrStar)...
Requirement Six: Determine if a word contains white space characters, but remove spaces
Whitespace characters include spaces, tabs, and line breaks, as shown in the following code:
func IsSpace(r rune) bool { // This property isn't the same as Z; special-case it. if uint32(r) <= MaxLatin1 { switch r { case '\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0: return true } return false } return isExcludingLatin(White_Space, r)}
The predicate that the letter is a whitespace character has not yet been implemented, and we define a predicate isspace with atomic semantics:
type IsSpace struct {}func (i IsSpace) Satisfy(c rune) bool { return unicode.IsSpace(c)}
The verbs with the letters x and Y have a combined semantic allof, where x is isspace,y to isnot{equals{'}, and the code is implemented as follows:
type AllOf struct { specs []CharSpec}func (a AllOf) Satisfy(c rune) bool { for _, spec := range a.specs { if !spec.Satisfy(c) { return false } } return true}
Determine whether word contains white space characters by exists, but remove spaces:
isSpaceAndNotBlank := AllOf{[]CharSpec{IsSpace{}, IsNot{Equals{' '}}}}ok := Exists(word, isSpaceAndNotBlank)...
Requirement Seven: Determine if a word contains the letter x and is not case sensitive
Case insensitive is a predicate ignorecase with cosmetic semantics, and the code is implemented as follows:
type IgnoreCase struct { spec CharSpec}func (i IgnoreCase) Satisfy(c rune) bool { return i.spec.Satisfy(unicode.ToLower(c))}
Use exists to determine whether a word word contains the letter x and is not case-sensitive:
isXIgnoreCase := IgnoreCase{Equals{'x'}}ok := Exists(word, isXIgnoreCase)...
Summary
In the process of implementation of the requirements, we have applied the orthogonal design four principle, so that many small or small functions, and then through the combination to complete a set of requirements, not only the field expression is strong, but also very flexible, easy to deal with changes.
The letter predicate is the focus of this article and has the following three categories
category |
predicate |
Atomic semantics |
IsDigit Isupper Equals IsSpace |
Cosmetic semantics |
IsNot IgnoreCase |
Combinatorial semantics |
AnyOf AllOf |
Its domain model is as follows:
Char-spec.png