单一职责原则
就一个类而言,应该仅有一个引起它变化的原因。降低变更的风险 一个类的职责越多,变更的可能性就越大,变更带来的风险也就越大
如果一个类有一个以上的职责,这些职责就耦合在了一起。一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。当一个职责发生变化时,可能会影响其它的职责。多个职责耦合在一起,会影响复用性。
单一职责原则的核心就是解耦和增强内聚性。
func main() {
c := Clothes{}
fmt.Println("在工作")
c.Onwork()
fmt.Println("在吃饭")
c.Oneat()
}
type Clothes struct {
}
func (c *Clothes) Onwork() {
fmt.Println("工作时穿的衣服。。。")
}
func (c *Clothes) Oneat() {
fmt.Println("吃饭时穿的衣服。。。")
}
/*
在工作
吃饭时穿的衣服。。。
在吃饭
吃饭时穿的衣服。。。
假设 Onwork方法中可能修改了类的一些属性,并且可能影响到 Oneat方法。
这个时候就可以做改进,让一个类负责一个职责,互不冲突
func main() {
work := ClothesWork{}
fmt.Println("在工作")
work.Style()
fmt.Println("在吃饭")
eat := ClothesEat{}
eat.Style()
}
type ClothesWork struct {
}
type ClothesEat struct {
}
func (c *ClothesWork) Style() {
fmt.Println("吃饭时穿的衣服。。。")
}
func (c *ClothesEat) Style() {
fmt.Println("吃饭时穿的衣服。。。")
}
/*
在工作
吃饭时穿的衣服。。。
在吃饭
吃饭时穿的衣服。。。
开闭原则
需求总是变化的, 如何才能做到不对原有系统修改的前提下,实现灵活的扩展,多扩展少修改原则。
核心就是面对抽象编程,抽象相对稳定。 让类依赖于固定的抽象,所以对修改是封闭的;而通过面向对象的继承和多态机制,可以实现对抽象体的继承,通过重写其方法来改变固有行为,实现新的扩展方法。
func main() {
b := Bank{}
b.Pay()
b.Transfer()
b.Save()
b.Stock()
}
type Bank struct {
}
func (receiver Bank) Pay() {
fmt.Println("支付中。。。。")
}
func (receiver Bank) Transfer() {
fmt.Println("转账中。。。。")
}
func (receiver Bank) Save() {
fmt.Println("存款中。。。。")
}
// 新增业务扩展
func (receiver Bank) Stock() {
fmt.Println("股票。。。。")
}
/*
支付中。。。。
转账中。。。。
存款中。。。。
股票。。。。
这里可能体现不出来修改了 Bank这个类,这是因为GO语言的特性,但是我们确实是扩展了 Bank这个类,假设类比较庞大,那这种方式可能会增大耦合,不知道其他的方法对这个类做了什么修改。
下面用接口继承的方式来修改,每次扩展新的类只需要继承接口重写方法
func main() {
BankerWork(&Save{})
BankerWork(&Pay{})
BankerWork(&Transfer{})
BankerWork(&Stock{})
}
type Banker interface {
Work()
}
func BankerWork(banker Banker) {
banker.Work()
}
type Save struct {
}
func (receiver Save) Work() {
fmt.Println("存款中。。。。")
}
type Pay struct {
}
func (receiver Pay) Work() {
fmt.Println("支付中。。。。")
}
type Transfer struct {
}
func (receiver Transfer) Work() {
fmt.Println("转账中。。。。")
}
type Stock struct {
}
func (receiver Stock) Work() {
fmt.Println("股票。。。。")
}
/*
存款中。。。。
支付中。。。。
转账中。。。。
股票。。。。
里氏替换原则
所谓替换,我的理解就是子类需要继承实现父类的所有抽象方法,当然子类也可以有自己的个性方法,也就是说必要时子类可以是父类的替身,子类当做父类使用扩展。
func main() {
worker1 := father{}
worker1.work()
worker2 := child{}
worker2.work()
worker2.play()
}
type Work interface {
work()
}
type father struct {
Work
}
type child struct {
father
}
func (receiver father) work() {
fmt.Println("父类的work方法。。。")
}
func (receiver child) work() {
fmt.Println("子类的work方法。。。")
}
func (receiver child) play() {
fmt.Println("子类的play方法。。。")
}
/*
父类的work方法。。。
子类的work方法。。。
子类的play方法。。。
依赖倒转原则
与里氏替换原则不同的是,依赖于抽象接口而不是具体的实现类。
里氏替换是子类和父类,依赖倒转是抽象和细节,面对的不是相对多变的子类,而是相对稳定的接口。
func main() {
宝马 := 宝马{}
奔驰 := 奔驰{}
张三 := 张三{}
李四 := 李四{}
张三.Drive_1(宝马)
李四.Drive_2(奔驰)
}
type 宝马 struct {
}
func (receiver 宝马) Run() {
fmt.Println("宝马启动了。。。")
}
type 奔驰 struct {
}
func (receiver 奔驰) Run() {
fmt.Println("奔驰启动了。。。")
}
type 张三 struct {
}
func (receiver 张三) Drive_1(car 宝马) {
fmt.Println("司机张三:")
car.Run()
}
func (receiver 张三) Drive_2(car 奔驰) {
fmt.Println("司机张三:")
car.Run()
}
type 李四 struct {
}
func (receiver 李四) Drive_1(car 宝马) {
fmt.Println("司机李四:")
car.Run()
}
func (receiver 李四) Drive_2(car 奔驰) {
fmt.Println("司机李四:")
car.Run()
}
/*
司机张三:
宝马启动了。。。
司机李四:
奔驰启动了。。。
这个例子就可以看出如果我们要扩展司机或者是扩展车辆都很麻烦,需要修改每个类。
下面抽象成接口,司机只需要完成司机的功能,汽车也只需要完成汽车功能,不在乎是谁和是谁来完成。
func main() {
宝马 := 宝马{}
奔驰 := 奔驰{}
本田 := 本田{}
张三 := 张三{}
李四 := 李四{}
张三.Drive(宝马)
李四.Drive(奔驰)
张三.Drive(本田)
}
type Car interface {
Run()
}
type Driver interface {
Drive()
}
type 宝马 struct {
}
func (receiver 宝马) Run() {
fmt.Println("宝马启动了。。。")
}
type 奔驰 struct {
}
func (receiver 奔驰) Run() {
fmt.Println("奔驰启动了。。。")
}
type 本田 struct {
}
func (receiver 本田) Run() {
fmt.Println("本田启动了。。。")
}
type 张三 struct {
}
func (receiver 张三) Drive(car Car) {
fmt.Println("司机张三:")
car.Run()
}
type 李四 struct {
}
func (receiver 李四) Drive(car Car) {
fmt.Println("司机李四:")
car.Run()
}
/*
司机张三:
宝马启动了。。。
司机李四:
奔驰启动了。。。
司机张三:
本田启动了。。。
接口隔离原则
从约束上看,单一职责原则注重对接口职责的划分,接口隔离原则更注重的是对接口依赖程度的隔离,换句话说就是不强迫用户的程序依赖他们不需要的接口方法,不应该把所有的操作都封装到一个接口中,使用多个专门的接口能够体现对象的层次,通过接口的继承实现总接口的定义。
接口尽量细化,需要什么接口提供什么接口,不必要的方法剔除到另一个接口中。
func main() {
student := Student{}
teacher := Teacher{}
student.view_score()
teacher.view_score()
teacher.delete_score()
teacher.save_score()
teacher.update_score()
}
type View_score interface {
view_score()
}
type Edit_score interface {
save_score()
update_score()
delete_score()
}
type Score interface {
View_score
Edit_score
}
type Student struct {
View_score
}
type Teacher struct {
Score
}
func (receiver Student) view_score() {
fmt.Println("学生查看成绩。。。")
}
func (receiver Teacher) view_score() {
fmt.Println("老师查看成绩。。。")
}
func (receiver Teacher) save_score() {
fmt.Println("老师保存成绩。。。")
}
func (receiver Teacher) update_score() {
fmt.Println("老师更新成绩。。。")
}
func (receiver Teacher) delete_score() {
fmt.Println("老师删除成绩。。。")
}
/*
学生查看成绩。。。
老师查看成绩。。。
老师删除成绩。。。
老师保存成绩。。。
老师更新成绩。。。
这个例子就可以很好体现接口隔离了,学生只有查看成绩的权限,而老师具有所有的权限。
假设这里如果是让学生继承 Score接口而不是拆分出View_score,后面还需要对学生不需要的特权方法重写,增大了耦合,同时以后扩展起来也还要都修改。
聚合复用原则
这种原则要求我们尽量优先使用组合的关联方式来实现,其次才考虑继承关系,因为如果使用继承,父类的任何修改都可能影响到子类的行为。如果使用继承应该严格遵守里氏替换原则。
func main() {
cat := BlackCat{}
cat.eat()
cat.slepp()
cat.play()
}
type Cat struct {
age int
name string
}
type BlackCat struct {
Cat
}
func (receiver Cat) eat() {
fmt.Println("小猫吃。。。")
}
func (receiver Cat) slepp() {
fmt.Println("小猫睡觉。。。")
}
func (receiver BlackCat) play() {
fmt.Println("黑猫玩耍。。。")
}
/*
小猫吃。。。
小猫睡觉。。。
黑猫玩耍。。。
这种就是继承的方式,父类的属性都被子类继承下来了,子类和父类的依赖耦合太高。
下面使用组合的方式复用。
func main() {
cat := Cat{}
blackcat := BlackCat{}
blackcat.eat(cat)
blackcat.sleep(cat)
blackcat.play()
}
type Cat struct {
age int
name string
}
type BlackCat struct {
}
func (receiver Cat) eat() {
fmt.Println("小猫吃。。。")
}
func (receiver Cat) slepp() {
fmt.Println("小猫睡觉。。。")
}
func (receiver BlackCat) eat(cat Cat) {
cat.eat()
}
func (receiver BlackCat) sleep(cat Cat) {
cat.slepp()
}
func (receiver BlackCat) play() {
fmt.Println("黑猫玩耍。。。")
}
/*
小猫吃。。。
小猫睡觉。。。
黑猫玩耍。。。
这个例子中就是通过让 Cat作为参数传进来组合,和 Cat有依赖关系的仅仅是传参进来的方法,降低了与父类之间的耦合。
也可以把父类当做一个属性组合在一起,尽量不动父类。
func main() {
blackcat := BlackCat{}
blackcat.cat.eat()
blackcat.cat.slepp()
blackcat.play()
}
type Cat struct {
age int
name string
}
type BlackCat struct {
cat Cat
}
func (receiver Cat) eat() {
fmt.Println("小猫吃。。。")
}
func (receiver Cat) slepp() {
fmt.Println("小猫睡觉。。。")
}
func (receiver BlackCat) play() {
fmt.Println("黑猫玩耍。。。")
}
/*
小猫吃。。。
小猫睡觉。。。
黑猫玩耍。。。
迪米特原则
最小知识法则,类和类之间最少依赖,有选择的暴露方法,类的依赖关系尽量简单独立,也就是说一个对象对其他对象应该保持尽可能的少的了解,降低之间耦合,提高可维护性
比如说买房子,在客户和房源之间添加个中介,客户不需要每个房源都去了解和每个房源之间做比较,同时客户也不是一个两个,可能是很多很多的对象,这个时候就需要添加中介帮助客户提前全部都了解,客户可直接通过中介就可以了解所有房源。
各个模块之间相互调用通过提供一个统一的接口实现,这样模块之间就不需要了解其内部细节,内部发生改变不影响其他模块调用。
但是这样的后果就是系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互调用关系,这在一定程度上增加了系统的复杂度。
func main() {
客户1 := 客户{name: "张三"}
客户2 := 客户{name: "李四"}
房东 := 房东{name: "王五"}
中介 := 中介{房源: 房东}
客户1.find_home(中介)
客户2.find_home(中介)
}
type Find_home interface {
find_home()
}
type Sale_home interface {
sale_home()
}
type 房东 struct {
name string
}
type 客户 struct {
name string
}
type 中介 struct {
房源 房东
}
func (receiver 房东) sale_home() {
fmt.Println(" -房东卖房子。。。")
}
func (receiver 客户) find_home(m 中介) {
fmt.Println(receiver.name, "-客户找房子。。。")
m.match_home()
}
func (receiver 中介) match_home() {
fmt.Print(receiver.房源.name)
receiver.房源.sale_home()
fmt.Println("匹配成功。。。")
}
/*
张三 -客户找房子。。。
王五 -房东卖房子。。。
匹配成功。。。
李四 -客户找房子。。。
王五 -房东卖房子。。。
匹配成功。。。
像这样,客户不需要了解所有的房源,不需要管房东是谁,房东不需要管客户是谁,客户要买房子直接找中介就可以。
如果有房子就进行下一步操作,减少了客户与所有房源之间的依赖,降低耦合但可能会增大复杂度。
本文介绍了单一职责原则、开闭原则、里氏替换原则和依赖倒转原则,强调如何通过这些原则降低类间的耦合,提高代码复用性和灵活性。通过实例展示了如何应用这些原则来改进代码结构和扩展性。
435

被折叠的 条评论
为什么被折叠?



