第一阶段 :方法和属性封装。面向对象是从面向过程发展来的,我们面向过程编写程序的时候,函数和属性是分开写的,这时候,参数的复用性较差,把属性和函数写在一起,属性不就可以复用了
第二阶段:方法和属性怎么封装和对象分层。这时候我们又发现了新的问题,一个对象(定义为低层对象)都在被另一个对象(定义为高层对象)使用。假定使用流程是,低层对象传入高层对象,高层对象处理低层对象,并且对低层对象的处理函数由高层对象实现。这样做有一个缺点,因为对低层对象的处理方式随着版本更新在不断变化,这时候就要不断修改高层对象。如何解决呢?将低层对象的处理方法由低层对象实现,高层对象只负责调用
以下h代表高层,l代表低层
第三阶段:抽象类。 类是由方法和属性组成的,本质还是方法的调用,那我们用方法代替类不就行了,依据这个思想出现了抽象类(只声明方法,但是不实现)。大家可以类比生活中的例子,我想要吃饭,只要说给我吃饭的工具就好了,至于是金筷子、银筷子还是木筷子,我并不指定,由提供餐具的老板去指定就好了
至此:对象被分为三层,分别是,高层、低层、抽象层
第四阶段:抽象类有啥用 一。 针对接口编程而不是针对实现编程。按照第二阶段编写程序,发现了新的问题,有时候写h类的时候,需要某个方法,这个方法是变化的模块,这时候我们可以在h类内只定义一个抽象类变量就可以了,l 层实现了,直接传进来就好
第五阶段:抽象类有啥用 二,针对接口编程而不是针对实现编程。这时又发现新问题,有个需求,h 层需要处理l 层的很多不同类型的对象,我们选择 I 层类的共有方法组成一个抽象类,这个抽象类定义的变量传入h类即可。
第六阶段:抽象类有啥用 三,针对接口编程而不是针对实现编程。如果l层的类实现了很多功能,这些功能有些是当前h层不需要的,完全没必要将I层对象传进来,在高层定义一个接口即可
总结:如果不采用抽象接口定义变量,高层就是高度耦合的,具体体现在,高层类和具体的低层类绑定,如果要使用别的类,高层就要加代码。并且不利于团队协作,假如高层不指定接口,低层屌丝不知道实现啥,可能是五花八门的
最后,重要的事情说三遍,针对接口编程而不是针对实现编程
高层和低层都是依赖抽象层,如下图。我们设计代码的时候也是这样,变化的模块从代码中抽离出一个抽象接口,由低层去实现。不断把变化的部分抽离成为一个个模块,使得低层的变化不会对高层产生影响,这就实现了下图。当然在程序编写之初,我们并不一定能明确的知道,哪个模块是经常变化的,也就抽象的不够好,这也就是代码重构的意义所在
总结:稳定(相对低层稳定)的代码写成非接口函数,变化的代码写成接口函数
我写了一个非常简单的标准的面向对象的三层代码:
从代码中可以看到,高层和低层都是依赖抽象层的。高层的稳定其实是由抽象层的稳定带来的
高层(相对低层稳定):
package high
import "go-devops/design_model/inter"
type High struct {
arr []inter.Inter
}
func(h *High) Append(interDatas ...inter.Inter){
for _,single := range interDatas {
h.arr = append(h.arr, single)
}
}
func(h *High) Handle(){
for _,single := range h.arr {
single.Eat()
}
}
抽象层(稳定):
package inter
type Inter interface {
Eat()
}
低层(变化):
package low
import "fmt"
type People struct {
Kind string
Food string
}
func(p *People) Eat() {
fmt.Printf("我是%v,我喜欢吃%v",p.Kind,p.Food)
}
type Dog struct {
Kind string
Food string
}
func(d *Dog) Eat() {
fmt.Printf("我是%v,我喜欢吃%vn",d.Kind,d.Food)
}
写代码测试下,能跑通:
package main
import (
h "design_model/high"
"design_model/low"
)
func main() {
dog := low.Dog{
Kind: "狗",
Food: "屎",
}
people := low.People{
Kind: "人",
Food: "肉",
}
high := h.High{}
high.Append(&dog,&people)
high.Handle()
}



