缘起

最近复习设计模式
拜读谭勇德的<<设计模式就该这样学>>
该书以java语言演绎了常见设计模式
本系列笔记拟采用golang练习之

开闭原则

  • 开闭原则(Open-Closed Principle, OCP)指一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。所谓开闭,也正是对扩展和修改两个行为的一个原则。
  • 实现开闭原则的核心思想就是面向抽象编程。

场景

  • 某线上学习平台, 提供系列课程产品(接口: ICourse)
  • 每个课程有id,name,price等属性
  • 现在平台搞促销, golang课程(GolangCourse)打六折
  • 如何上架打折课程? 是直接修改原golang课程的价格, 还是增加折后golang课程?

思路

  • 开闭原则, 就是尽量避免修改, 改以扩展的方式, 实现系统功能的增加
  • 增加”优惠折扣”接口 - IDiscount
  • 增加”折后golang课程” - DiscountedGolangCourse, 同时实现课程接口和折扣接口
  • 课程接口继承自原GolangCourse接口, 添加实现折扣接口, 并覆盖ICourse.price()方法

ICourse.go

principles/open_close/ICourse.go
课程接口

  1. package open_close
  2. type ICourse interface {
  3. ID() int
  4. Name() string
  5. Price() float64
  6. }

GolangCourse.go

principles/open_close/GolangCourse.go
golang课程类, 实现ICourse接口

  1. package open_close
  2. type GolangCourse struct {
  3. iID int
  4. sName string
  5. fPrice float64
  6. }
  7. func NewGolangCourse(id int, name string, price float64) ICourse {
  8. return &GolangCourse{
  9. iID: id,
  10. sName: name,
  11. fPrice: price,
  12. }
  13. }
  14. func (me *GolangCourse) ID() int {
  15. return me.iID
  16. }
  17. func (me *GolangCourse) Name() string {
  18. return me.sName
  19. }
  20. func (me *GolangCourse) Price() float64 {
  21. return me.fPrice
  22. }

IDiscount.go

principles/open_close/IDiscount.go
折扣接口

  1. package open_close
  2. type IDiscount interface {
  3. Discount() float64
  4. }

DiscountedGolangCourse.go

principles/open_close/DiscountedGolangCourse.go
该课程同时实现ICourse和IDiscount接口

  1. package open_close
  2. type DiscountedGolangCourse struct {
  3. GolangCourse
  4. fDiscount float64
  5. }
  6. func NewDiscountedGolangCourse(id int, name string, price float64, discount float64) ICourse {
  7. return &DiscountedGolangCourse{
  8. GolangCourse: GolangCourse{
  9. iID: id,
  10. sName: name,
  11. fPrice: price,
  12. },
  13. fDiscount : discount,
  14. }
  15. }
  16. // implements IDiscount.Discount
  17. func (me *DiscountedGolangCourse) Discount() float64 {
  18. return me.fDiscount
  19. }
  20. // overwrite ICourse.Price
  21. func (me *DiscountedGolangCourse) Price() float64 {
  22. return me.fDiscount * me.GolangCourse.Price()
  23. }

open_close_test.go

main/open_close_test.go
课程接口测试用例

  1. package main
  2. import (
  3. "testing"
  4. )
  5. import (ocp "learning/gooop/principles/open_close")
  6. func Test_open_close(t *testing.T) {
  7. fnShowCourse := func(it ocp.ICourse) {
  8. t.Logf("id=%v, name=%v, price=%v\n", it.ID(), it.Name(), it.Price())
  9. }
  10. c1 := ocp.NewGolangCourse(1, "golang课程", 100)
  11. fnShowCourse(c1)
  12. c2 := ocp.NewDiscountedGolangCourse(2, "golang优惠课程", 100, 0.6)
  13. fnShowCourse(c2)
  14. }

测试

  1. $> go test -v main/open_close_test.go
  2. === RUN Test_open_close
  3. open_close_test.go:10: id=1, name=golang课程, price=100
  4. open_close_test.go:10: id=2, name=golang优惠课程, price=60
  5. --- PASS: Test_open_close (0.00s)
  6. PASS
  7. ok command-line-arguments 0.001s